zoukankan      html  css  js  c++  java
  • [题解] CF995E Number Clicker

    题目链接(洛谷)

    题目大意

    给定两个数 (u)(v) 。有三种操作:

    1. (u=u+1(mod) (p))
    2. (u=u+p−1(mod) (p))
    3. (u=u^{p−2}(mod) (p))

    求最小能把 (u) 变为 (v) 的操作步数。

    思路

    BFS

    状态太多导致队列装不下。

    迭代加深

    (TLE) ,浪费了太多时间在每一层上,没有记忆化且状态很多。

    IDA*

    不行,无法得出乐观估价函数。

    双向BFS

    这样会将步数很为两半,状态相较于普通的 (BFS) 会少很多。

    先来看操作一和操作二,他们的关系是可以互逆的。一个对于原数 (+1) ,另一个对于原数 (-1)

    操作三和操作三是互逆的,由费马小定理可知:若 (p) 为质数,则 (a^{p-1}≡1(mod) (p))

    可得出:((u^{p-2})^{p-2}≡u^{(p-2)(p-2)}≡u^{(p-1)(p-3)+1}≡(u^{p-1})^{p-3}u≡u(mod) (p))

    那么就分别由开始状态与结束状态来向中间推进。

    Code

    #include <map>
    #include <queue>
    #include <cstdio>
    #include <iostream>
    using namespace std;
    struct Status {//队列中保存的状态
    	int step, number, flag;//分别是:步数,当前状态的数,正向或者反向
    	Status() {}
    	Status(int S, int N, int F) {
    		step = S;
    		number = N;
    		flag = F;
    	}
    };
    const int MAXN = 1e6 + 5;
    queue<Status> q;
    map<int, int> real;
    bool vis[2][MAXN];//是否访问过
    int dis[2][MAXN];//步数
    pair<int, int> pre[2][MAXN];//first记录前一个数的哈希值,second记录操作的序号
    int u, v, p;
    int tot;
    int Quick_Pow(int fpx, int fpy) {//快速幂
    	long long res = 1;
    	long long x = fpx;
    	long long y = fpy;
    	while(y) {
    		if(y & 1)
    			res = (res * x) % p;
    		x = (x * x) % p; 
    		y >>= 1;
    	}
    	int ans = res;
    	return ans;
    }
    int Get_Hash(int x) {//map映射假哈希
    	map<int, int>::iterator it = real.find(x);
    	if(it != real.end())
    		return (*it).second;
    	real[x] = ++tot;
    	return tot;
    }
    void Print(int x, int y) {//输出路径:记录每个前缀
    	if(y == -1)
    		return;
    	if(!x) {//前半部分倒着输出
    		if(pre[x][y].first != -1) {
    			Print(x, pre[x][y].first);
    			printf("%d ", pre[x][y].second);
    		}
    	}
    	else {//后半部分正着输出
    		if(pre[x][y].first != -1) {
    			printf("%d ", pre[x][y].second);
    			Print(x, pre[x][y].first);
    		}
    	}
    }
    void DoubleBfs() {
    	int tmp;
    	q.push(Status(0, u, 0));//初始化两个状态
    	q.push(Status(0, v, 1));
    	tmp = Get_Hash(u);
    	vis[0][tmp] = 1;
    	pre[0][tmp].first = -1;
    	tmp = Get_Hash(v);
    	vis[1][tmp] = 1;
    	pre[1][tmp].first = -1;
    	while(!q.empty()) {
    		Status now = q.front();
    		q.pop();
    		int skt = Get_Hash(now.number);
    		if(vis[!now.flag][skt]) {//碰头了输出并跳出
    			printf("%d
    ", dis[!now.flag][skt] + dis[now.flag][skt]);
    			if(pre[0][skt].first != -1) {
    				Print(0, pre[0][skt].first);
    				printf("%d ", pre[0][skt].second);
    			}
    			if(pre[1][skt].first != -1) {
    				printf("%d ", pre[1][skt].second);
    				Print(1, pre[1][skt].first);
    			}
    			return;
    		}
    		Status next = now;
    		next.step++;
    		next.number = (next.number + 1) % p;
    		tmp = Get_Hash(next.number);
    		if(!vis[now.flag][tmp]) {//没有被访问则访问
    			vis[now.flag][tmp] = 1;
    			dis[now.flag][tmp] = next.step;
    			pre[now.flag][tmp].first = skt;
    			if(now.flag)
    				pre[now.flag][tmp].second = 2;//若是倒着的,则该操作为1
    			else
    				pre[now.flag][tmp].second = 1;//若是正着的,则该操作为2
    			q.push(next);
    		}
    		next = now;
    		next.step++;
    		next.number = (next.number + p - 1) % p;
    		tmp = Get_Hash(next.number);
    		if(!vis[now.flag][tmp]) {//同上
    			vis[now.flag][tmp] = 1;
    			dis[now.flag][tmp] = next.step;
    			pre[now.flag][tmp].first = skt;
    			if(now.flag)
    				pre[now.flag][tmp].second = 1;
    			else
    				pre[now.flag][tmp].second = 2;
    			q.push(next);
    		}
    		next = now;
    		next.step++;
    		next.number = Quick_Pow(next.number, p - 2) % p;
    		tmp = Get_Hash(next.number);
    		if(!vis[now.flag][tmp]) {//同上
    			vis[now.flag][tmp] = 1;
    			dis[now.flag][tmp] = next.step;
    			pre[now.flag][tmp].first = skt;
    			pre[now.flag][tmp].second = 3;//自己的逆操作就是自己
    			q.push(next);
    		}
    	}
    }
    int main() {
    	scanf("%d %d %d", &u, &v, &p);
    	DoubleBfs();
    	return 0;
    }
    
  • 相关阅读:
    【SQL】SQL Server登录常见问题
    【毒思】纪念我死去的爱情
    【毒思】化蝶双飞
    VS2013常用快捷键你敢不会?
    SSIS 更新变量
    一次SSIS Package的调试经历
    binary 和 varbinary 用法全解
    Execute SQL Task 第二篇:返回结果集
    OLEDB 命令转换组件的用法
    脚本组件的用法
  • 原文地址:https://www.cnblogs.com/C202202chenkelin/p/14507927.html
Copyright © 2011-2022 走看看