51nod1038 X^A Mod P 原根+BSGS

本文探讨了模指数方程XA=B(modP)的求解方法,通过引入原根概念和利用BSGS算法,实现了解的高效寻找。文章详细解释了如何通过枚举找到原根,并使用扩展欧几里得算法解决同余方程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description
XAmodP=BX^A mod P = BXAmodP=B,其中PPP为质数。给出PPPAAA BBB,求&lt;P&lt; P<P的所有XXX
例如:P=11,A=3,B=5P = 11,A = 3,B = 5P=11A=3B=5
33Mod11=53^3 Mod 11 = 533Mod11=5
所有数据中,解的数量不超过Sqrt(P)Sqrt(P)Sqrt(P)


Sample Input
3
11 3 5
13 3 1
13 2 2


Sample Output
3
1 3 9
No Solution


很久以前就做了这道题,一直没有写博客,今天写主要是怕以后忘记。

有关原根:
定义:设gggPPP的原根那么与PPP互质的数,都可以由ggg为底数的次幂表示出来。
一个数有原根,当且仅当这个数为:P=1,2,4,pa,2paP=1,2,4,p^a,2p^aP=1,2,4,pa,2pa
一个数PPP,它的原根数量为phi(phi(P))phi(phi(P))phi(phi(P))
最小的原根一般比较小,于是我们考虑暴力枚举。
判断一个数ggg是否是PPP的原根:
1.1.1.暴力枚举。
2.2.2.(P−1)(P-1)(P1)的约数中有一个质数满足g(P−1)/pi==1(modP)g^{(P-1)/pi}==1(mod P)g(P1)/pi==1(modP),那么就不是原根。

说到这道题:
式子是这样的:
XA=B(modP)X^A =B(mod P)XA=B(modP)
你设gt1=X(modP)g^{t1}=X(modP)gt1=X(modP)gt2=B(modP)g^{t2}=B(modP)gt2=B(modP)
那么式子就是这样的:
gt1A=gt2(modP)g^{t1A}=g^{t2}(modP)gt1A=gt2(modP)
所以:t1A=t2(modP−1)t1A=t2(mod P-1)t1A=t2(modP1)


#include <cmath>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;
const int mod = 1000007;

int plen, cnt;
LL o[110000];
LL head[1100000], num[110000], id[110000], next[110000];
vector<LL> a;

LL pow_mod(LL x, LL k, LL mod) {
	LL ans = 1; x %= mod;
	while(k) {
		if(k % 2 == 1) (ans *= x) %= mod;
		(x *= x) %= mod; k /= 2;
	} return ans;
}

int Find(LL x) {
	LL y = x - 1;
	for(int i = 2; i * i <= x; i++) {
		if(y % i == 0) {
			a.push_back(i);
			while(y % i == 0) y /= i;
		}
	} if(y > 1) a.push_back(y);
	int o;
	for(int i = 1; ;i++) {
		bool bk = 1;
		for(int j = 0; j < a.size(); j++) {
			if(pow_mod(i, (x - 1) / a[j], x) == 1) {
				bk = 0; break;
			}
		} if(bk) {o = i; break;}
	}
	return o;
}

int BSGS(int a, int b, int p) {
	cnt = 0;
	LL t = sqrt(p) + 1, u = 1;
	for(int i = 0; i < t; i++) {
		LL x = ((LL)b * u) % p;
		num[++cnt] = x; id[cnt] = i;
		int oo = x % mod;
		if(!head[oo]) head[oo] = cnt;
		else {
			int y = head[oo];
			while(next[y]) y = next[y];
			next[y] = cnt;
		}
		(u *= a) %= p;
	}
	a = pow_mod(a, t, p);
	if(a == 0) {
		if(b == 0) return 1;
		return -1;
	} u = 1;
	for(int i = 0; i <= t; i++) {
		LL x = u;
		int z = x % mod;
		z = head[z];
		while(num[z] != x && z) z = next[z];
		if(num[z] == x) {
			LL j = id[z];
			if(i * t - j >= 0) return i * t - j;
		} (u *= a) %= p;
	} return -1;
}

LL exgcd(LL a, LL b, LL &x, LL &y) {
	if(b == 0) {
		x = 1; y = 0;
		return a;
	} else {
		LL tx, ty;
		LL d = exgcd(b, a % b, tx, ty);
		x = ty; y = tx - ty * (a / b);
		return d;
	}
}

int main() {
	int tt; scanf("%d", &tt);
	while(tt--) {
		LL p, a, b, x, y; scanf("%lld%lld%lld", &p, &a, &b);
		LL root = Find(p);
		memset(head, 0, sizeof(head));
		memset(next, 0, sizeof(next));
		memset(num, 0, sizeof(num));
		memset(id, 0, sizeof(id));
		LL c = BSGS(root, b, p);
		if(c == -1) {printf("No Solution\n"); continue;}
		b = p - 1;
		LL d = exgcd(a, b, x, y);
		if(c % d != 0) {printf("No Solution\n"); continue;}
		(x *= c / d) %= b;
		int len = 0;
		for(int i = 1; i <= d; i++) {
			(x += b / d) %= b; (x += b) %= b;
			o[++len] = pow_mod(root, x, p);
		} sort(o + 1, o + len + 1);
		for(int i = 1; i < len; i++) printf("%d ", o[i]);
		printf("%d\n", o[len]);
	}
	return 0;
}
### 关于51Nod 3100 上台阶问题的C++解法 #### 题目解析 该题目通常涉及斐波那契数列的应用。假设每次可以走一步或者两步,那么到达第 \( n \) 层台阶的方法总数等于到达第 \( n-1 \) 层和第 \( n-2 \) 层方法数之和。 此逻辑可以通过动态规划来解决,并且为了防止数值过大,需要对结果取模操作(如 \( \% 100003 \)[^1])。以下是基于上述思路的一个高效实现: ```cpp #include <iostream> using namespace std; const int MOD = 100003; long long f[100010]; int main() { int n; cin >> n; // 初始化前两项 f[0] = 1; // 到达第0层有1种方式(不移动) f[1] = 1; // 到达第1层只有1种方式 // 动态规划计算f[i] for (int i = 2; i <= n; ++i) { f[i] = (f[i - 1] + f[i - 2]) % MOD; } cout << f[n] << endl; return 0; } ``` 以上代码通过数组 `f` 存储每层台阶的结果,利用循环逐步填充至目标层数 \( n \),并最终输出结果。 --- #### 时间复杂度分析 由于仅需一次线性遍历即可完成所有状态转移,时间复杂度为 \( O(n) \)。空间复杂度同样为 \( O(n) \),但如果优化存储,则可进一步降低到 \( O(1) \): ```cpp #include <iostream> using namespace std; const int MOD = 100003; int main() { int n; cin >> n; long long prev2 = 1, prev1 = 1, current; if (n == 0 || n == 1) { cout << 1 << endl; return 0; } for (int i = 2; i <= n; ++i) { current = (prev1 + prev2) % MOD; prev2 = prev1; prev1 = current; } cout << prev1 << endl; return 0; } ``` 在此版本中,只保留最近两个状态变量 (`prev1`, `prev2`) 来更新当前值,从而节省内存开销。 --- #### 输入输出说明 输入部分接受单个整数 \( n \),表示台阶数量;程序会返回从地面走到第 \( n \) 层的不同路径数目,结果经过指定模运算处理以适应大范围数据需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值