【ybt金牌导航8-6-3】【luogu P4195】【模板】扩展BSGS

扩展BSGS

题目链接:ybt金牌导航8-6-3 / luogu P4195

题目大意

给出 a,p,b,找到最小的非负整数 x,使得 a 的 x 次方在模 p 意义下等于 b。

思路

这个题就是扩展 BSGS 的模板题。

BSGS

那既然它是扩展,那我们来说说原版是怎么弄的。
BSGS 的意思是先小步后大步,它可以求出方程 A x ≡ B ( m o d   C ) A^x\equiv B(mod\ C) AxB(mod C) gcd ⁡ ( A , C ) = 1 \gcd(A,C)=1 gcd(A,C)=1 时的解。而且是用 C \sqrt{C} C 的时间。

O ( C ) O(C) O(C) 的大家都会把,就枚举 0 ∼ C − 1 0\sim C-1 0C1,有就输出,否则就是没有解。
但是为什么答案会出现在这里呢?
你会知道要么有很多种答案,要么没有答案。因为你 A x A^x Ax 的取值在取模意义下只能是 0 ∼ C − 1 0\sim C-1 0C1,那一旦你有取值一样的,那跟下来也是一样。(因为你每次乘的都是同一个数)
那在最坏情况下,把范围内所有数都取完再重复,就是 O ( C ) O(C) O(C) 了。

然后 BSGS 就是一种神奇的算法,通过有点分块的感觉把它弄成了 O ( C ) O(\sqrt{C}) O(C )
它是这样的,首先把 0 ∼ C − 1 0\sim C-1 0C1 分成连续的几组,让每组的个数都尽可能相等。
然后对于每组,我们询问在这个组中的数是否可以成为答案。

那你会问了,它还是 O ( C ) O(C) O(C) 的啊!
不急,我们看公式来优化。

我们来看对于块 i i i 1 ≤ i ≤ n 1\leq i\leq n 1in n n n 是分成的块数),给每个点从左到右编号 0 ∼ m − 1 0\sim m-1 0m1(也就是每块 m m m 个,那会有 m = C n m=\dfrac{C}{n} m=nC),我们枚举每个编号 y y y,我们就是要看是否存在一个使得这个公式满足:
A i m − y ≡ B ( m o d   C ) A^{im-y}\equiv B(mod\ C) AimyB(mod C)

那我们开始化简:
首先,我们看到 A i m − y A^{im-y} Aimy 上面有减的,而模数是质数,那我们就弄个逆元,然后再移到右边,就变成:
A i m ≡ A y B ( m o d   C ) A^{im}\equiv A^yB(mod\ C) AimAyB(mod C)
那你会发现,每次你枚举的 y y y 都是同样的 0 ∼ m − 1 0\sim m-1 0m1,那你可以把它预处理出来。然后你看左边,你会发现它只枚举了一个 i i i

但是你是不可以每个都枚举另一边出现的每个可能,看是否存在相等的。(因为你数组存不了)
数组存不了,你自然会想到一个东西——哈希表。

那就把右边能取的每个值都放进哈希表里面,然后每次查询 A i m A^{im} Aim 对应的数是否在哈希表中有。

然后就是 n , m n,m n,m 的取值问题了,因为你枚举左边是 O ( n ) O(n) O(n),预处理右边是 O ( m ) O(m) O(m),那你要尽可能让它们相等,那就都取 C \sqrt{C} C

那就是 BSGS 算法了,它的确定就是一定要 gcd ⁡ ( A , C ) = 1 \gcd(A,C)=1 gcd(A,C)=1

扩展 BSGS

它就是来解决 BSGS 的缺点,使得它可以解决 gcd ⁡ ( A , C ) ≠ 1 \gcd(A,C)\neq1 gcd(A,C)=1 的情况。

它主要的想法还是把它弄回互质的情况,然后再用 BSGS 解决。

我们观察式子:
A x ≡ B ( m o d   C ) A^{x}\equiv B(mod\ C) AxB(mod C)
那因为要化简,那我们弄成不定方程。
A x + C y = B A^{x}+Cy= B Ax+Cy=B

那我们就不停地把左边的化简到 gcd ⁡ \gcd gcd 1 1 1,但是 A x A^{x} Ax 太大了,那我们考虑把它分成 x x x A A A
每次就除以 gcd ⁡ ( A , C ) \gcd(A,C) gcd(A,C),然后知道 gcd ⁡ ( A x , C y ) = 1 \gcd(A^{x},Cy)=1 gcd(Ax,Cy)=1,然后 B B B 也跟着除,如果不能整除了,就说明无解。

那我们假设一共除以了 w w w 次,第 i i i 次除的是 d i d_i di
那式子就变成了这个:
A w ∑ i = 1 w d i A x − w + C y ∑ i = 1 w d i = B ∑ i = 1 w d i \dfrac{A^w}{\sum\limits^{w}_{i=1}d_i}A^{x-w}+\dfrac{Cy}{\sum\limits^{w}_{i=1}d_i}=\dfrac{B}{\sum\limits^{w}_{i=1}d_i} i=1wdiAwAxw+i=1wdiCy=i=1wdiB

这个时候,就互质了。
然后你就可以转回同余方程,按着 BSGS 算了。

扩展欧几里得求逆元

不会吧不会吧不会真有人不会吧。
哦我不会啊,那没事了。

其实就是 a x + m y = 1 ax+my=1 ax+my=1 解这个方程。( m m m 是模数)
很明显它可以解决模数和要逆元的数互质的情况。

为什么能这么求呢?
变成同于方程:
a x ≡ 1 ( m o d   m ) ax\equiv 1(mod\ m) ax1(mod m)
那你要 x x x a a a 的逆元,其实就是要满足这个式子。
(因为逆元定义就是取模意义下的倒数,那数和它的倒数相乘当然就是 1 1 1 了)

代码

#include<cmath>
#include<cstdio>
#include<cstring>
#define ll long long
#define hash_mo 999979

using namespace std;

struct node {
    
    
	ll to, nxt, x;
}e[500001];
ll a, mo, b, n, now, t, KK;
ll hash[1000001], gcd, d, ans;

void csh() {
    
    //初始化
	t = 0;
	d = 1;
	memset(hash, 0, sizeof(hash));
	KK = 0;
}

void hash_push(ll x, ll id) {
    
    //放一个数在哈希表中
	int pl = x % hash_mo;
	for (int i = hash[pl]; i; i = e[i].nxt)
		if (e[i].to == x) {
    
    
			e[i].x = id;
			return ;
		}
	e[++KK] = (node){
    
    x, hash[pl], id}; hash[pl] = KK;
}

ll hash_ask(int q) {
    
    //查询一个数是否在哈希表中存在
	int pl = q % hash_mo;
	for (int i = hash[pl]; i; i = e[i].nxt)
		if (e[i].to == q) {
    
    
			return e[i].x;
		}
	
	return -1;
}

ll GCD(ll x, ll y) {
    
    //求最大公因子
	if (!y) return x;
	return GCD(y, x % y);
}

ll exgcd(ll a, ll &x, ll b, ll &y) {
    
    //扩展欧几里得
	if (!b) {
    
    
		x = 1;
		y = 1;
		return a;
	}
	
	ll re = exgcd(b, y, a % b, x);
	y -= a / b * x;
	
	return re;
}

ll ksm(ll x, ll y) {
    
    //快速幂
	ll re = 1;
	while (y) {
    
    
		if (y & 1) re = (re * x) % mo;
		x = (x * x) % mo;
		y >>= 1;
	}
	return re;
}

ll inv(ll x, ll mo) {
    
    //扩展欧几里得求逆元
	ll X, Y;
	exgcd(x, X, mo, Y);
	return (X % mo + mo) % mo;
}

ll get_hz() {
    
    
	gcd = GCD(a, mo);
	while (gcd != 1) {
    
    //一直除以最大公因数
		if (b % gcd != 0) return 0;//b已经不能除了,说明无解
		t++;
		mo /= gcd;
		b /= gcd;
		d = (d * (a / gcd) % mo) % mo;
		
		gcd = GCD(a, mo);
	}
	return 1;
}

ll make_hash() {
    
    
	n = sqrt(mo);
	
	now = 1;
	for (int i = 0; i < n; i++) {
    
    //把每个 b*a^y 插入到哈希中
		if (now == b) return i + t;
		hash_push(now * b % mo, i);
		now = (now * a) % mo;
	}
	
	int ntimes = ksm(a, n);
	now = ntimes;
	for (int i = 1; i <= n; i++) {
    
    //判断每个 a^(im) 是否与任何一个 b*a^y 相等。
		int mz = hash_ask(now);
		if (mz != -1) return i * n + t - mz;
		now = (now * ntimes) % mo;
	}
	
	return -1;//所有都不相等,无解
}

int main() {
    
    
	scanf("%lld %lld %lld", &a, &mo, &b);
	while (a || mo || b) {
    
    
		csh();
		
		if (!get_hz()) printf("No Solution\n");//先化简成互质的,不能就说明无解
			else {
    
    
				b = (b * inv(d, mo)) % mo;//算出互质的式子中的 b
				
				ans = make_hash();//解互质的式子
				
				if (ans == -1) printf("No Solution\n");
					else printf("%lld\n", ans);
			}
		scanf("%lld %lld %lld", &a, &mo, &b);
	}
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43346722/article/details/114187551