【SGU 261】BSGS算法详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/giftedpanda/article/details/100566893

BSGS(baby-step gaint-step),大步小步算法,该算法可以在O(\sqrt p)时间复杂度内求解

                                                           a^x \equiv b \ mod \ p

gcd(a, p) = 1, 0 \leq x < p

算法描述

我们令x = A\left \lceil \sqrt p\ \right \rceil - B0 \leq A, B <= \left \lceil \sqrt p\ \right \rceil,这样我们可以保证 0 \leq x < p

a^{A\left \lceil \sqrt p \ \right \rceil - B} \equiv b \ mod \ p

\Leftrightarrow a^{A \left \lceil \sqrt p\ \right \rceil} \equiv b a^B \ mod\ p

由于我们知道a,b,我们可以先枚举B算出右边 ba^B\ mod \ p,存在一个hash表里面,然后再枚举A计算左边的a^{A\left \lceil \sqrt p\ \right \rceil}的值,然后去判断hash表里面是否有这个值,如果有,我们就得到了 x = A\left \lceil \sqrt p\ \right \rceil - B

原根

gcd(g, m) = 1, g^{\varphi(m)} \equiv 1\ mod \ mg 为 m 的一个原根。

所有原根

gm的一个原根,则集合S = \left\{ g^s | 1 \leq s \leq \varphi(m)\right, gcd(s, m) = 1\}包含所有原根,由此如果m有原根,则m一共有\varphi(\varphi(m))个原根关于模m两两互不同余

一个原根

gcd(g, m) = 1,p_1,p_2,p_3,...,p_k\varphi(m)的不同素因子,当且仅当 对于任意的 1 \leq i \leq k, g^{\frac{\varphi(m)}{p_i}} \not\equiv 1 \ mod \ m 成立,gm的一个原根

了解了原根以后,我们就可以在仅当p是素数时,就可以求解方程 x^a \equiv b \ mod \ p

x^a \equiv b \ mod \ p,由于p是一个素数,则p一定存在一个原根g,因此对于模p下的任意x(0 \leq x < p),存在唯一一个i(0 \leq i < p-1)满足 x = g^i

于是我们令x = g^c,所以(g^c)^a \equiv b \ mod \ p \Leftrightarrow (g^a)^c \equiv b\ mod \ p,于是这就转换成了一个BSGS模型,可以算出c

x_0 \equiv g^c\ mod \ p

求出一个解以后,如何得到所有解

x_0 \equiv g^c \ mod \ n, g^{\varphi(n)} \equiv 1\ mod \ n

\forall t \in z, x^k \equiv g^{ck} \equiv g^{ck + t\varphi(n)} \ mod \ n

\forall t \in z, k|t\cdot\varphi(n), x \equiv g^{c+ \frac{t\cdot\varphi(n)}{k}} \ mod \ n

既然,k|t\cdot\varphi(n),那么 \frac{k}{gcd(k, \varphi(n))} | t,我们令 t = i * \frac{k}{gcd(k, \varphi(n))}

于是,\forall \ i \in z, x \equiv g^{c + \frac{\varphi(n)}{gcd(k, \varphi(n))}*i}\ mod \ n

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b)  // 求最大公约数
{
	if(b == 0) return a;
	while(b) {
		ll t = a;
		a = b;
		b = t % b;
	}
	return a;
}
ll power(ll a, ll b, ll p) // 快速幂
{
	ll ans = 1;
	while(b) {
 		if(b & 1) ans = (ans * a) % p;
		a = (a * a) % p;
		b >>= 1;
	}
	return ans;
}
ll generator(ll p)  // 求模p的原根
{
	vector<ll> fact;
	ll phi = p - 1, n = phi;  // phi(p) = p - 1, p 为素数
	for(ll i = 2; i * i <= n; i++) {  // 素因子分解
		if(n % i == 0) {
			fact.push_back(i);
			while(n % i == 0) n /= i;
		}
	}
	if(n > 1) fact.push_back(n);
	for(ll res = 2; res <= p; res++) {  // 枚举每一可能的值
		bool ok = true;
		for(vector<ll>::iterator it = fact.begin(); it != fact.end(); it++) {
			if(power(res, phi / (*it), p) == 1) {  // 对于每一个素因子p_i, a^{phi(p)/p_i} != 1 mod p 才为原根
				ok = false;
				break;
			}
		} 
		if(ok) return res;
	}
	return -1;
}
void BSGS(ll k, ll a, ll n)  // x^k = a mod n
{
	if(a == 0) {  
		printf("1\n0\n");
		return ;
	}
	ll g = generator(n); // 原根
	ll sq = (ll)sqrt(n + .0) + 1;  // sqrt(n) 向上取整
	vector<pair<ll, int> > dec(sq); 
	// 枚举 A
	for(ll i = 1; i <= sq; i++) dec[i-1] = make_pair(power(g, i * sq * k % (n - 1), n), i);
	sort(dec.begin(), dec.end());
	ll res = -1;
	// 枚举 B
	for(int i = 1; i <= sq; i++) {
		ll my = power(g, i * k % (n - 1), n) * a % n;
		vector<pair<ll, int> >::iterator it = lower_bound(dec.begin(), dec.end(), make_pair(my, 0));
		if(it != dec.end() && it->first == my) {
			res = it->second * sq - i;  // A sqrt(n) - B
			break;
		}
	}
	if(res == -1) {
		printf("0\n");
		return ;
	}
	// 得到所有的答案
	ll delta = (n - 1) / gcd(n - 1, k);  
	vector<ll> ans;
	for(ll cur = res % delta; cur < n-1; cur += delta) ans.push_back(power(g, cur, n));
	sort(ans.begin(), ans.end());
	printf("%d\n", ans.size());
	for(vector<ll>::iterator it = ans.begin(); it != ans.end(); it++) cout << *it << endl;
	return ;
}
int main()
{
	ll p, k, a;
	while(scanf("%lld %lld %lld", &p, &k, &a) == 3) {
		BSGS(k, a, p);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/giftedpanda/article/details/100566893
sgu