• 【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^xequiv B(mod C))(gcd(A,C)=1) 时的解。而且是用 (sqrt{C}) 的时间。

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

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

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

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

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

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

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

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

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

    扩展 BSGS

    它就是来解决 BSGS 的缺点,使得它可以解决 (gcd(A,C) eq1) 的情况。

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

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

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

    那我们假设一共除以了 (w) 次,第 (i) 次除的是 (d_i)
    那式子就变成了这个:
    (dfrac{A^w}{sumlimits^{w}_{i=1}d_i}A^{x-w}+dfrac{Cy}{sumlimits^{w}_{i=1}d_i}=dfrac{B}{sumlimits^{w}_{i=1}d_i})

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

    扩展欧几里得求逆元

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

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

    为什么能这么求呢?
    变成同于方程:
    (axequiv 1(mod m))
    那你要 (x)(a) 的逆元,其实就是要满足这个式子。
    (因为逆元定义就是取模意义下的倒数,那数和它的倒数相乘当然就是 (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
    ");//先化简成互质的,不能就说明无解
    			else {
    				b = (b * inv(d, mo)) % mo;//算出互质的式子中的 b
    				
    				ans = make_hash();//解互质的式子
    				
    				if (ans == -1) printf("No Solution
    ");
    					else printf("%lld
    ", ans);
    			}
    		scanf("%lld %lld %lld", &a, &mo, &b);
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1056:点和正方形的关系
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1056:点和正方形的关系
    信息学奥赛一本通(C++)在线评测系统——基础(三)数据结构 —— 1339:【例34】求后序遍历
    信息学奥赛一本通(C++)在线评测系统——基础(三)数据结构 —— 1339:【例34】求后序遍历
    信息学奥赛一本通(C++)在线评测系统——基础(三)数据结构 —— 1339:【例34】求后序遍历
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1055:判断闰年
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1055:判断闰年
    python 连接sqlserver
    python 连接sqlserver
    python中__name__的意义以及作用
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_8-6-3.html
Copyright © 2020-2023  润新知