BSGS学习笔记

VI.BSGS(大步小步算法)

欢迎来到 北上广深 拔山盖世 比赛搞事 不算个事 算法学习现场。

BSGS,全名 Baby Step Giant Step 算法,是用于求解 axb(modp),其中 gcd(a,p)=0 的算法。

我们记 K=p 。则 x 即可表示成 cKd,其中 0d<K 的形式。

于是 acKdb(modp),则 (aK)cbad(modp)。于是我们只需预处理出 b 乘上 a 的所有 d 次幂,扔进哈希表中,然后依次枚举 aK 次幂的 c 次幂进行求解即可。

若使用 map,复杂度为 O(nlogn);若使用手写哈希表或 unordered_map,复杂度为 O(n)

VI.I.[TJOI2007] 可爱的质数/【模板】BSGS

代码:

#include<bits/stdc++.h>
using namespace std;
int a,b,p,K;
unordered_map<int,int>mp;
int main(){
	scanf("%d%d%d",&p,&a,&b),K=sqrt(p);
	int P=1;
	for(int j=0;j<K;j++)mp[1ll*P*b%p]=j,P=1ll*P*a%p;
	for(int i=1,j=P;i<=K;i++,j=1ll*j*P%p)if(mp.find(j)!=mp.end()){printf("%lld\n",1ll*i*K-mp[j]);return 0;}
	puts("no solution");
	return 0;
}

VI.II.[SDOI2013] 随机数生成器

我们来考虑一下:

日期 原式子 化简后式子
1 X X
2 aX+b aX+b
3 a(aX+b)+b a2X+(a+1)b
4 a(a(ax+b)+b)+b a3X+(a2+a+1)b
i ai1X+bj=0i2aj

于是我们要寻找 ai1X+bj=0i2ajT(modp) 的解。

考虑上等比数列求和公式,得到 ai1X+b×ai11a1T(modp)

稍微整理一下 ai1(ba1+X)T+ba1(modp)

扔过去,得到 ai1T+ba1ba1+X(modp)

OK!是BSGS的形式了!直接上就行了……个锤子。

还要特判一堆东西。

  1. X=T

这个东西虽然不很特殊,但是因为我们接下来众多特判都要判这个,所以就在开头统一判掉。答案:1

  1. a=0

这时,除了第一天是 X,其余天都是 b。因为我们已经特判了 X=T 的情形,所以只要特判 b=T 的情形,是则答案为 2,否则答案为 1

  1. a=1

这时,无法使用等比数列求和,式子为 X+(i1)bT(modp)。整理得 i=T+bXb

但是,还要继续特判!如果 b=0,显然只有 X=T 时答案为 1(这种情况已在开头特判过),否则答案为 1

否则,若 T+bX=0,上式出来的结果是 0,要手动将其判作 p,因为 p 是质数,要 pb 才能绕一圈。

  1. ba1+X=0

此种情形下,无法除过去。除非 T+ba1 也为 0,否则答案即为 1。但是这就意味着有 X=T,已经特判过了,所以不用再考虑这种情形,碰到直接判 1 即可。

  1. i1=0

常规BSGS一般不会考虑这种情形。但是,这就意味着 i=1,我们已经在最开头特判过了,故不用考虑。

代码:

#include<bits/stdc++.h>
using namespace std;
int T_T,p,a,b,X,T;
int ksm(int x,int y=p-2){
	int z=1;
	for(;y;y>>=1,x=1ll*x*x%p)if(y&1)z=1ll*z*x%p;
	return z;
}
unordered_map<int,int>mp;
int main(){
	scanf("%d",&T_T);
	while(T_T--){
		scanf("%d%d%d%d%d",&p,&a,&b,&X,&T);
		if(X==T){puts("1");continue;}
		if(!a){
			if(T==b)puts("2");else puts("-1");
			continue;
		}
		if(a==1){
			T=(0ll+T-X+b+p)%p;
			if(!b)puts("-1");else{
				T=1ll*T*ksm(b)%p;
				if(T)printf("%d\n",T);
				else printf("%d\n",p);
			}
			continue;
		}
		b=1ll*b*ksm(a-1)%p;
		X=(X+b)%p,T=(T+b)%p;
		if(!X){puts("-1");continue;}
		T=1ll*T*ksm(X)%p;
//		printf("%d %d\n",a,T);
		mp.clear();
		int K=sqrt(p);
		int P=1;
		for(int j=0;j<K;j++)mp[1ll*P*T%p]=j,P=1ll*P*a%p;
		bool fd=false;
		for(int i=1,j=P;i<=K+1;i++,j=1ll*j*P%p)if(mp.find(j)!=mp.end()){printf("%d\n",i*K-mp[j]+1),fd=true;break;}
		if(!fd)puts("-1");
	}
	return 0;
}

VI.III.CF1106F Lunar New Year and a Recursive Sequence

首先,我们不妨设 fk=x。则,因为递推式里面全都是一坨坨东西的幂再乘一起,初始的元素中又仅有 fk 可能不为 1,所以明显每个元素都可以被表示成 xa 的形式,其中 a 是我们接下来要求的东西。

我们发现,因为底数是相乘的,所以放在指数上就是相加,可以使用矩阵快速幂处理,只不过因为在指数上,所以要模 φ(p)

现在我们知道 a 了,则要求解的是 xab(modp) 的形式。如何求解?

因为 998244353 有着地球人都知道的原根 g=3,所以我们不妨令 x=gc。于是原式转换为 (gc)ab(modp),继续转换得 (ga)cb(modp)。因为 ga 已知,所以就是BSGS模板,随便套套就套出 c 来了。

虽然原题上说的是求”任意的 x“,但是现行洛谷翻译上给的却是求”最小的 x“,我也就照着翻译写了。因此我们这里介绍一下求最小 x 的做法。

首先,x=gc 一定是一个合法解;又因为 g 最短的循环节是 φ(p),所以就有 tZ,xagac+tφ(p)b(modp)。明显所有这样的 t 构成全部合法解的集合(但是会有重复的)。

这就得到了 a|tφ(p),xgc+tφ(p)ab(modp)。所有这样的 t 得到唯一的解集合。

a|tφ(p) 唯一等价于 agcd(a,φ(p))|t。于是我们设 t=agcd(a,φ(p))×i,就得到了解集为 i[0,gcd(a,φ(p)))Z,gc+φ(p)gcd(a,φ(p))×i

现在,我们考虑找到上述集合中最小的数。显然,当 gcd(a,φ(p)) 不算很大(比如 106 以内),可以枚举所有的 imin;而当 gcd(a,φ(p)) 较大时,集合中的数又不会很稀疏,可以从 1 开始一个一个枚举check它是否在集合内(当然,使用BSGS)。

时间复杂度比较玄学,取决于你取的分界点。反正我取了 220 作为分界点。

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int phi=998244352;
int n,m,A;
struct Matrix{
	int a[110][110];
	Matrix(){memset(a,0,sizeof(a));}
	int*operator[](const int&x){return a[x];}
	friend Matrix operator*(Matrix&u,Matrix&v){
		Matrix w;
		for(int i=1;i<=A;i++)for(int j=1;j<=A;j++)for(int k=1;k<=A;k++)(w[i][j]+=1ll*u[i][k]*v[k][j]%phi)%=phi;
		return w;
	}
	void print()const{for(int i=1;i<=A;i++){for(int j=1;j<=A;j++)printf("%d ",a[i][j]);puts("");}}
}M;
int ksm(int x,int y){
	int z=1;
	for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;
	return z;
}
int KSM(){
	Matrix I;
	for(int i=1;i<=A;i++)I[i][i]=1;
	for(int y=n-A;y;y>>=1,M=M*M)if(y&1)I=I*M;
	return I[A][A];
}
unordered_map<int,int>mp;
int BSGS(int a,int b){
	if(b==0)return 1;
	mp.clear();
	int K=sqrt(mod);
	int P=1;
	for(int i=0;i<K;i++,P=1ll*P*a%mod)mp[1ll*P*b%mod]=i;
	for(int i=1,j=P;i<=K+2;i++,j=1ll*j*P%mod)if(mp.find(j)!=mp.end())return i*K-mp[j];
	return -1;
}
int main(){
	scanf("%d",&A);
	for(int i=1;i<=A;i++)scanf("%d",&M[A-i+1][A]);
	for(int i=2;i<=A;i++)M[i][i-1]=1;
//	M.print();
	scanf("%d%d",&n,&m);
	int a=KSM();
	int c=BSGS(ksm(3,a),m);
	if(c==-1){puts("-1");return 0;}
	int K=__gcd(a,phi),R=phi/K;
	if(K<=1048576){
		int res=mod;
		for(int i=0,j=ksm(3,c),k=ksm(3,R);i<K;i++,j=1ll*j*k%mod)res=min(res,j);
		printf("%d\n",res);
	}else for(int i=1;;i++)if(BSGS(3,i)%R==c%R){printf("%d\n",i);return 0;}
	return 0;
}
posted @   Troverld  阅读(65)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示