学习笔记——同余数论

前置芝士#

同余概念,逆元,费马小定理,Exgcd,欧拉函数及定理。

(扩展)中国剩余定理#

个人喜欢使用非公式形式的合并方式来求解同余方程组。

我们需要求解的问题是求合法的解 x,使之满足:

{xa1(modp1)xa2(modp2)xan(modpn)

此时我们考虑一个人类智慧来合并两个方程组,如果我们能做到这一点,那么我们最终能把方程组变成一个方程,从而得到通解。所以现在我们对:

{xa1(modp1)xa2(modp2)

进行合并。对 xa1(modp1),我们容易改写成:x=a1+k1p1。那换句话说,我们希望找到一个 k1,使得 a1+k1p1a2(modp2),那我们再次故技重施,得到:a1+k1p1=a2k2p2,移项就得到了:k1p1+k2p2=a2a1,这玩意儿直接扩欧解一下就行了。

在此之前,我们实际上是构造了一个 x0(mod1) 的方程作为边界来合并的。

可喜可贺的是这做法不需要保证 gcd(p1,p2,,pn)=1

  • P4777 【模板】扩展中国剩余定理(EXCRT)
#define int long long
using namespace std;
const int MAXN=1e5;
int exgcd(int a,int b,int &x,int &y){
	if(b==0){x=1,y=0;return a;}
	int g=exgcd(b,a%b,y,x);
	y-=a/b*x;return g;
}
int a[MAXN],p[MAXN];
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int n;cin>>n;
	rep(i,1,n) cin>>p[i]>>a[i];
	int ans=0,lcm=1,x,y,g;
	rep(i,1,n){
		int A=lcm,B=p[i],C=((a[i]-ans)%p[i]+p[i])%p[i];
		g=exgcd(A,B,x,y);x=(x%B+B)%B;
		ans+=(__int128)(C/g)*x%(B/g)*lcm%(lcm*=p[i]/g);
		ans%=lcm;
	}if(ans<0) ans+=((-ans-1)/lcm+1)*lcm;
	cout<<ans<<'\n';
	return 0;
}
  • P4774 [NOI2018] 屠龙勇士

这题在求出了每次选用的剑之后,和例题的差别就是 x 多了系数,其实本质是相同的,重新推一遍就行了:

我们肯定是想着把系数消掉的,所以我们假装已经消掉了此前的系数,即 xa(modp)(可以看成是系数为 1,你可以发现上面我们构造的边界条件正是这样的)。假如说,它要与方程 b1xa1(modp1) 合并,那么我们故技重施,得到 b1(a+k1p)=a1k2p1,移项得到 b1pk1+p1k2=a1ab1,那么 k1,k2 又可以用扩欧来解了。

My Code

#define int long long
using namespace std;
const int MAXN=1e5+10;
int exgcd(int a,int b,int &x,int &y){
	if(b==0){x=1,y=0;return a;}
	int g=exgcd(b,a%b,y,x);
	y-=a/b*x;return g;
}
int a[MAXN],p[MAXN],b[MAXN],r[MAXN];
multiset<int> s;
void solve(){
	int n,m;cin>>n>>m;
	s.clear();
	rep(i,1,n) cin>>a[i];
	rep(i,1,n) cin>>p[i];
	rep(i,1,n) cin>>r[i];
	int atk;
	rep(i,1,m){
		cin>>atk;
		s.insert(atk);
	}
	int mx=0;
	rep(i,1,n){
		auto it=s.upper_bound(a[i]);
		if(it!=s.begin()) b[i]=*(--it);
		else b[i]=*it;
		s.erase(it);s.insert(r[i]);
		mx=max(mx,(a[i]-1)/b[i]+1);
	}
	int ans=0,lcm=1;
	rep(i,1,n){
		int A=(__int128)b[i]*lcm%p[i],B=p[i],C=(a[i]-b[i]*ans%p[i]+p[i])%p[i];
		int x,y,g=exgcd(A,B,x,y);x=(x%B+B)%B;
		if(C%g){cout<<"-1\n";return;}
		ans+=(__int128)(C/g)*x%(B/g)*lcm%(lcm*=B/g);
		ans%=lcm;
	}
	if(ans<mx) ans=ans+((mx-ans-1)/lcm+1)*lcm;
	cout<<ans<<'\n';
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int T;for(cin>>T;T--;)solve();
	return 0;
}

离散对数问题#

这是这样一类问题:求解 axb(modp)。由 aφ(p)1(modp),可知必然有 φ(p) 以内的解。

BSGS#

复杂度 O(p),求解离散对数的最小自然数解。考虑分块思想,令 B=p,那么假如说我们有一个解是 x,将其分解成 kB+j,那么肯定有 k,jp。于是我们带入方程:akB+jb(modp),移项得到:ajb×akB(modp)。基于此,我们先预处理出 aj,j[0,B1],存入哈希表中,然后枚举计算 a0,aB,a2B。每次带入看哈希表中是否有对应的值,然后就能求出解了。

为了 exBSGS 的方便,这里采用一种不需要求逆元的方法。上面的等式其实可以写成:

akBbaj(modp)

那我们把 baj 存入哈希表,然后查询就可以了,最后答案是 kBj

int BSGS(int a,int b,int p){
	unordered_map<int,int> J;
	//这个哈希表可以用 pbds 代替,可能安全一点
	int A=1,B=(int)ceil(sqrt(p));
	rep(i,0,B) J[A*b%p]=i,A=A*((i==B)?1:a)%p;
	int BA=A;A=1;
	rep(k,0,B){
		if(J.count(A)&&k*B>=J[A]) return k*B-J[A];
		A=A*BA%p;
	}return -1;
}

exBSGS#

如果说 p 不是质数,那么假如 a 不互质了就没法逆元,我们需要转化成 a,p 互质的情况。我们考虑到 a 可以有很多很多次,我们不妨每次把整个式子除以 gcd(a,p),然后把 a 拆下来,即如果有 g=gcd(a,p),则:

agax1bg(modpg)

如果 b 不整除 g,那就无解。

然后我们看 gcd(pg,a) 是否为 1,否则继续做上面这个,直到 gcd(pg,a)=1。我们记 G=i=1kgi,则这个式子变成了:

axkbG(akG)1(modpG)

然后利用 BSGS,可以求出 xk,再回带就可以了。注意这里的逆元可能需要扩欧。

int exgcd(int a,int b,int &x,int &y){
	if(b==0){x=1;y=0;return a;}
	int g=exgcd(b,a%b,y,x);
	y-=a/b*x;return g;
}
int inv(int a,int p){
	int x,y;exgcd(a,p,x,y);
	x=(x%p+p)%p;return x;
}
int BSGS(int a,int b,int p){
	unordered_map<int,int> J;
	int A=1,B=(int)ceil(sqrt(p));
	rep(i,0,B) J[A*b%p]=i,A=A*((i==B)?1:a)%p;
	int BA=A;A=1;
	rep(k,0,B){
		if(J.count(A)&&k*B>=J[A]) return k*B-J[A];
		A=A*BA%p;
	}return -1;
}
int exBSGS(int a,int b,int p){
	b%=p;
	if(b==1||p==1) return 0;
	int k=0,g,A=1;
	while((g=__gcd(p,a))!=1){
		if(b%g) return -1;
		b/=g;p/=g;(A*=a/g)%=p;
		k++;if(A==b) return k;
	}
	int res=BSGS(a,b*inv(A,p)%p,p);
	if(res==-1) return -1;
	return res+k;
}
  • P4861 按钮

得到方程:

Kx1(modM)

然后直接 exBSGS 就能求出最小解了。

My Code
#define int long long
using namespace std;
int exgcd(int a,int b,int &x,int &y){
	if(b==0){x=1;y=0;return a;}
	int g=exgcd(b,a%b,y,x);
	y-=a/b*x;return g;
}
int inv(int a,int p){
	int x,y;exgcd(a,p,x,y);
	x=(x%p+p)%p;return x;
}
int BSGS(int a,int b,int p){
	unordered_map<int,int> J;
	int A=1,B=(int)ceil(sqrt(p));
	rep(i,0,B) J[A*b%p]=i,A=A*((i==B)?1:a)%p;
	int BA=A;A=1;
	rep(k,0,B){
		if(J.count(A)&&k*B>J[A]) return k*B-J[A];
		A=A*BA%p;
	}return -1;
}
int exBSGS(int a,int b,int p){
	b%=p;
	int k=0,g,A=1;
	while((g=__gcd(p,a))!=1){
		if(b%g) return -1;
		b/=g;p/=g;(A*=a/g)%=p;
		k++;if(A==b) return k;
	}
	int res=BSGS(a,b*inv(A,p)%p,p);
	if(res==-1) return -1;
	return res+k;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int m,k;cin>>m>>k;
	int res=exBSGS(k,1,m);
	if(res==-1) cout<<"Let's go Blue Jays!\n";
	else cout<<res<<'\n';
	return 0;
}
posted @   ZCETHAN  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示
主题色彩