[题解] 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\n", 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;
}
posted @ 2021-03-09 21:38  Last_Breath  阅读(156)  评论(0编辑  收藏  举报