BSGS&exBSGS

一、BSGS

1. 简介

baby(boy) step giant(girl) step算法,用于处理 \(a^x\equiv b(mod\ p)\),给 \(a,b,p\)\(a,p\) 互质) 求所有的 \(x\)问题

本质上有一点点像二分搜索的意味在里面

2. 算法流程

​ 有 \(a^x \equiv b\mod p\)

​ 令 \(x = A \lceil \sqrt p \rceil - B\),其中 \(0\leq A,B \leq \lceil\sqrt p \rceil\)

\(\Rightarrow a^{A\lceil \sqrt p \rceil - B} \equiv b\mod p\)

\(\Rightarrow a^{A\lceil \sqrt p \rceil} \equiv b \times a^B\mod p\)

我们可以把所有的 \(b\times a^i,i\in [1,\lceil \sqrt p\rceil]\) 存在一个映射数据结构(hash/map/unordered_map)上

枚举 \(A\) (\(1\rightarrow n\)),在映射数据结构上查找是否含有 \(a^{A\lceil\sqrt p \rceil}\)

3. 时间复杂度

​ 显然地,复杂度为 \(O(\sqrt p)\),(因为 \(A,B\leq \sqrt p\)

​ 如果使用 map/unordered_map 则为 \(O(\log_p \sqrt p)\)

注:建议做 UVA10225,洛谷 P3846 数据有亿点水

4. 警戒点

  • \(\sqrt p\) 一定要上取整
  • \(b\times a^i\)i 从0->n!
  • \(a^i\)i 从1->n!

5. 代码

typedef long long ll;
map<ll,ll>  Map;
ll BSGS(ll a,ll b,ll p){
	Map.clear();
	ll t = ceil(sqrt(p));//必须加上ceil
	ll k = 1;
	Map[b%p] = 0;
	for(ll i = 1; i <= t; ++i)Map[(k = k * a % p) * b % p] = i;//计算b*a^i
	a = 1;
	for(ll i = 1; i <= t; ++i){
		a = a * k % p;
		if(Map.count(a))//在映射数组中找a^i
			return i*t-Map[a];
	}
	return -1;
}
int main(){
	ll p,b,n,ans;
	while(~scanf("%lld%lld%lld",&p,&b,&n))
		if((ans = BSGS(b,n,p)) != -1)printf("%lld\n",ans);
		else puts("no solution");
	return 0;
}

二、exBSGS

1. 简介

exBSGS \(a^x\equiv b(\bmod\ p)\),给 \(a,b,p\) \((a,p\)不互质\()\)求所有的 \(x\)问题

2. 算法实现

​ 此时 \(a\times a^{x-1} \equiv b(\bmod\ p)\),令 \(d = gcd(a,p)\)

​ 左右同除 \(d\)

\(\therefore \frac{a}{d}\times a^{x-1} \equiv \frac{b}{d}(\bmod \frac{p}{d})\)

​ 特别的,当 \(d\nmid b\) 则无解

​ 就可以转化为 \(a^{x-1} \equiv b^{'}\times k^{-1} (\bmod\ p^{'})\),其中 \(p^{'} = \frac{p}{d},k = \frac{a}{d},b^{'} = \frac{b}{d}\)

​ 继续递归,直到 \(a,p\) 互质,那么再跑一遍 BSGS 即可

3. 警戒点

(1) 为了节省时间,我们将每次的 \(k\) 累乘,最后再求逆元。但是会出现一个问题,当 \(\prod k = b\) (当前的)时,我们实际上的 \(b_{real} = \frac{b}{\prod k} = 1\),此时需要退出递归,并返回 \(0\)

hack数据:a = 18 b = 16 p = 14 ans = 2

(2) 计算累乘时应该对下一轮的 \(p\) 取模

(3) 我们要在递归前对 \(a,b\) 取模,让 \(a,b < p\)

(4) 初始时 \(b = 1\)\(p = 1\),我们可以直接输出 \(0\),因为 \(x = 0\) 时满足条件

hack数据:a = 18 b = 49 p = 48 ans = 0

(5) 计算逆元的时候需要使用 exgcd 而不是 快速幂 因为 \(\prod k,p\) 可能不互质

4. 代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
map<ll,ll>  Map;
ll BSGS(ll a,ll b,ll p){
	Map.clear();
	ll t = ceil(sqrt(p));
	ll k = 1;
	Map[b%p] = 0;
	for(ll i = 1; i <= t; ++i)Map[(k = k * a % p) * b % p] = i;
	a = 1;
	for(ll i = 1; i <= t; ++i){
		a = a * k % p;
		if(Map.count(a))
			return i*t-Map[a];
	}
	return -1;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){x = 1;y = 0;return a;}
	ll d = exgcd(b,a%b,x,y);
	ll t = x;
	x = y;
	y = t - (a/b) * y;
	return d;
}
ll inv(ll a,ll b){
	ll x,y;
	exgcd(a,b,x,y);
	return (x % b + b) % b;
}
ll gcd(ll x,ll y){return y ? gcd(y,x%y) : x;}
ll x = 1;//即上面说的k
ll exBSGS(ll a,ll b,ll p){
	ll d = gcd(a,p);
	if(b == x)return 0;//相等判定
	if(d == 1){
		return BSGS(a,b*inv(x,p) % p,p);//使用exgcd
	}
	if(b % d != 0)return -1;
	else{
		x = a/d * x % (p/d);
		ll res = exBSGS(a,b/d,p/d); 
		if(res == -1)return -1;
		return res + 1;
	}
}
ll a,p,b;
int main(){
	while(~scanf("%lld%lld%lld",&a,&p,&b)){
		x = 1;
		if(a == 0 && b == 0 && p == 0)break;
		a%=p;b%=p;
		if(b==1||p==1){
			puts("0");continue;//递归之前判定
		}
		ll res = exBSGS(a%p,b%p,p);
		if(res == -1)puts("No Solution");
		else printf("%lld\n",res);
	}
} 
posted @ 2023-01-15 22:14  ricky_lin  阅读(26)  评论(0编辑  收藏  举报