【7】同余学习笔记
前言
中国剩余定理是大玄学,我终于懂得了龟速乘有什么用。没什么逻辑
实际上大纲没有专门列出同余本身的整体评级,但是大部分知识点都是
UPD on
长文警告:本文一共
欧几里得算法
欧几里得算法
最大公约数:最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个,记作
互质数:公因数只有
求
int gcd(int a,int b)
{
if(b==0)return a;
else return gcd(b,a%b);
}
实际上就是辗转相除法,还有辗转相减法,不过没有辗转相除法快。
裴蜀定理
给定一个关于未知数
这个式子也被称之为裴蜀等式。
方程有整数解,当且仅当
原问题等价于
首先有一点是显然的,最终的答案一定为
设
于是
因为
同理假设
综上所述,
裴蜀定理的一个常见推论是,正整数
扩展欧几里得算法
我们讨论裴蜀等式较为特殊的情况,如下所示:
在欧几里得算法的最后,一定会有
我们逆推扩展欧几里得算法的过程,则有
对比二式系数得到
对于更一般的
扩展欧几里得算法既可以求出
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
int r=exgcd(b,a%b,x,y);
int d=x;
x=y;
y=d-a/b*y;
return r;
}
中国剩余定理
中国剩余定理
求解如下同余方程组:(最小非负整数解)
其中
令
扩展中国剩余定理
求解如下同余方程组:(最小非负整数解)
其中
考虑两两合并同余方程组,具体的,对于如下两个式子,我们希望将其合并为一个同余式。
由小学学过的知识,我们知道,合并之后的模数
我们首先将两个式子用同余式的定义展开得到:
在这一步中,下面的式子展开为
由于两个式子右边都等于
移项得到如下式子:
这一是个经典的裴蜀等式,直接运用 exgcd 求解即可。最后把求出的值带回去合并时最开始方程组两个式子中的第一个式子,得到
经过多次合并,最后整个方程组被合并为了一个式子,如下:
显然,最小非负整数解一定为
代码中 mul 为慢速乘,作用是防止溢出,相当于 x=x*(c/q)%(a[i]/q)。
代码中的 -1。
long long excrt()
{
long long nm=a[1],ans=b[1];
for(int i=2;i<=n;i++)
{
long long c=((b[i]-ans)%a[i]+a[i])%a[i];
long long q=exgcd(nm,a[i],x,y);
if(c%q!=0)return -1;
x=mul(x,c/q,a[i]/q);
ans+=x*nm,nm*=(a[i]/q),ans=(ans%nm+nm)%nm;
}
return (ans%nm+nm)%nm;
}
应用
带系数的同余方程组(见例
square-free-number 降低模数(见例
欧拉定理
欧拉定理及其推论
欧拉定理:若正整数
欧拉定理的推论:若正整数
证明:
设
扩展欧拉定理
对于任意正整数
BSGS
BSGS(大步小步算法)
给定正整数
由欧拉定理,得到
考虑根号分治。令
两边同时乘以
考虑分别枚举左右两边。先枚举右边,
总的时间复杂度为
代码中 h.count(x) 表示数值
unordered_map<long long,long long>h;
long long bsgs(long long a,long long b,long long mod)
{
h.clear();
long long t=sqrt(mod)+1,now=1,inc=power(a,t,mod);
if(a==0)
{
if(b==0)return 1;
else return -1;
}
for(int i=0;i<=t-1;i++)h[now*b%mod]=i+1,now=now*a%mod;
now=1;
for(int i=1;i<=t;i++)
{
now=now*inc%mod;
if(h.count(now)>0)return i*t-h[now]+1;
}
return -1;
}
exBSGS(扩展大步小步算法)
给定正整数
考虑将原问题转化为
设
令
设最后一共进行了这个过程
根据上述转化,最终
对这个式子直接求一遍 bsgs,注意左边需要额外乘以一个系数
long long exbsgs(long long a,long long b)
{
long long d=gcd(a,p),cnt=0,sum=1,ans=0;
a%=p,b%=p;
if(b==1||p==1)return 0;
if(a==0)
{
if(b==0)return 1;
else return -1;
}
while(d!=1)
{
if(b%d!=0)return -1;
cnt++,p/=d,sum=sum*(a/d)%p,b/=d;
d=gcd(a,p);
if(sum==b)return cnt;
}
exgcd(sum%p,p,x,y);
x=(x%p+p)%p;
ans=bsgs(a,x*b%p,p);
if(ans==-1)return -1;
else return ans+cnt;
}
例题
例题
P4549 【模板】裴蜀定理
我们不难把裴蜀定理推广到多个整数的形式,证明过程几乎是完全一样的,最后的和一定是这些整数的
#include <bits/stdc++.h>
long long n,a,ans;
long long gcd(long long a,long long b)
{
if(b==0)return a;
else return gcd(b,a%b);
}
int main()
{
scanf("%lld",&n);
scanf("%lld%lld",&a,&ans);
if(a<0)a=-a;
if(ans<0)ans=-ans;
ans=gcd(a,ans);
for(int i=2;i<n;i++)
{
scanf("%lld",&a);
if(a<0)a=-a;
ans=gcd(a,ans);
}
printf("%lld",ans);
return 0;
}
例题
P1082 [NOIP2012 提高组] 同余方程
由同余的性质得到:
合并,去掉模数限制得到:
然后就转化成了裴蜀等式,扩展欧几里得算法解决。实际上就是求出了
#include <bits/stdc++.h>
using namespace std;
int n,a,b,x,y;
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
int r=exgcd(b,a%b,x,y);
int d=x;
x=y;
y=d-a/b*y;
return r;
}
int main()
{
scanf("%d%d",&a,&b);
int r=exgcd(a,b,x,y);
printf("%d\n",(x+b)%b);
return 0;
}
例题
P2421 [NOI2002] 荒岛野人
由于题目保证了答案不会超过
首先,如果连
然后,考虑枚举每两个野人在有生之年会不会到同一个山洞。抽象成数学,需要解一个同余方程:(其中只有
移项合并得:
采用类似例题
直接扩展欧几里得算法解决,求出
#include <bits/stdc++.h>
using namespace std;
long long x,y,s,c1[16],p1[16],l1[16],max1=0;
long long gcd(long long a,long long b)
{
if(b==0)return a;
else return gcd(b,a%b);
}
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
long long r=exgcd(b,a%b,x,y);
long long d=x;
x=y;
y=d-a/b*y;
return r;
}
int check(long long a,long long b,long long m,long long n,long long l)
{
long long p=n-m,q=a-b;
if(q%gcd(p,l)!=0)return 0;
long long r=exgcd(p,l,x,y);
return ((x*q/r)%(l/r)+(l/r))%(l/r);
}
bool judge(long long a)
{
for(int i=0;i<s;i++)
for(int j=i+1;j<s;j++)
{
long long b=check(c1[i],c1[j],p1[i],p1[j],a);
if(b==0)continue;
if(b<=l1[i]&&b<=l1[j])return 0;
}
return 1;
}
int main()
{
scanf("%lld",&s);
for(int i=0;i<s;i++)
{
scanf("%lld%lld%lld",&c1[i],&p1[i],&l1[i]);
max1=max(max1,c1[i]);
}
for(int i=max1;i<1000000;i++)
{
if(judge(i))
{
printf("%lld",i);
return 0;
}
}
return 0;
}
例题
P4777 【模板】扩展中国剩余定理(EXCRT)
扩展中国剩余定理板子题,不多赘述。
#include <bits/stdc++.h>
using namespace std;
long long n,x,y,a[200000],b[200000];
long long mul(long long a,long long p,long long m)
{
long long x=a,ans=0;
while(p)
{
if(p%2==1)ans=(ans+x)%m;
p/=2;
x=(x+x)%m;
}
return ans;
}
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
long long r=exgcd(b,a%b,x,y);
long long d=x;
x=y;
y=d-a/b*y;
return r;
}
long long excrt()
{
long long nm=a[1],ans=b[1];
for(int i=2;i<=n;i++)
{
long long c=((b[i]-ans)%a[i]+a[i])%a[i];
long long q=exgcd(nm,a[i],x,y);
if(c%q!=0)return -1;
x=mul(x,c/q,a[i]/q);
ans+=x*nm,nm*=(a[i]/q),ans=(ans%nm+nm)%nm;
}
return (ans%nm+nm)%nm;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld",&a[i],&b[i]);
printf("%lld",excrt());
return 0;
}
例题
P4774 [NOI2018] 屠龙勇士
由于必须按照顺序打每一条龙,每一条龙掉落到剑是一定的,所以如果能打到某一条龙,那么所可以选择的剑的集合是一定的。需要一个可以动态维护前驱,插入,删除元素的数据结构,考虑平衡树。
依题意,巨龙最后会不断以固定的速度回复血量,生命值刚好为
对于第
能打死所有的龙,需要所有同余式都有一个解。由题意,这个解是相同的。所以联立所有式子,得到如下方程组:
接下来,就需要求解带系数的同余方程组。不可以直接将
由小学学过的知识,我们知道,合并之后的模数
我们首先将两个式子用同余式的定义展开得到:
将上面式子乘以
由于两个式子右边都等于
移项得到如下式子:
这一是个经典的裴蜀等式,直接运用 exgcd 求解即可。最后把求出的值带回去合并时最开始方程组两个式子中的第一个式子,得到
经过多次合并,最后整个方程组被合并为了一个式子,如下:
显然,最小非负整数解一定为
注意多使用慢速乘,防止溢出。同时注意,必须要把巨龙的血量砍为负数。如果最后求出的
#include <bits/stdc++.h>
using namespace std;
long long t,n,m,x,y,s[200040],a[200040],b[200040],c[200040],z[200040],ch[200040][2],val[200040],key[200040],siz[200040],tol[200040],cnt=0,root=0;
long long create(long long v)
{
val[++cnt]=v;
key[cnt]=rand();
siz[cnt]=1;
tol[cnt]=1;
return cnt;
}
void pushup(long long now)
{
siz[now]=siz[ch[now][0]]+siz[ch[now][1]]+tol[now];
}
void build()
{
root=create((long long)-1999999999999),ch[root][1]=create((long long)1999999999999);
pushup(root);
}
void rotate(long long &now,long long to)
{
long long tmp=ch[now][to^1];
ch[now][to^1]=ch[tmp][to];
ch[tmp][to]=now;
now=tmp;
pushup(ch[now][to]);pushup(now);
}
void insert(long long &now,long long v)
{
if(now==0)
{
now=create(v);
return;
}
if(v==val[now])tol[now]++;
else
{
long long to=0;
if(v<val[now])to=0;
else to=1;
insert(ch[now][to],v);
if(key[now]<key[ch[now][to]])rotate(now,to^1);
}
pushup(now);
}
void del(long long &now,long long v)
{
if(now==0)return ;
if(v==val[now])
{
if(tol[now]>1)
{
tol[now]--;
pushup(now);
return ;
}
else if(ch[now][0]||ch[now][1])
{
if(!ch[now][1]||key[ch[now][0]]>key[ch[now][1]])rotate(now,1),del(ch[now][1],v);
else rotate(now,0),del(ch[now][0],v);
pushup(now);
}
else now=0;
return ;
}
else
{
long long to=0;
if(v<val[now])to=0;
else to=1;
del(ch[now][to],v);
pushup(now);
}
}
long long pre(long long x)
{
long long now=root,ans=0;
while(now)
{
if(x<val[now])now=ch[now][0];
else ans=val[now],now=ch[now][1];
}
return ans;
}
long long nxt(long long x)
{
long long now=root,ans=0;
while(now)
{
if(x>=val[now])now=ch[now][1];
else ans=val[now],now=ch[now][0];
}
return ans;
}
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
long long r=exgcd(b,a%b,x,y),d=x;
x=y,y=d-a/b*y;
return r;
}
long long mul(long long a,long long p,long long m)
{
long long x=a,ans=0;
while(p)
{
if(p&1)ans=(ans%m+x%m)%m;
p>>=1;
x=(x%m+x%m)%m;
}
return ans;
}
long long excrt()
{
long long nm=1,ans=0;
for(long long i=1;i<=n;i++)
{
long long d=((b[i]-mul(ans,c[i],a[i]))%a[i]+a[i])%a[i],r=exgcd(mul(nm,c[i],a[i]),a[i],x,y);
if(d%r!=0)return -1;
x=(x%a[i]+a[i])%a[i];
ans=ans+mul(nm,mul(x,(d/r),a[i]/r),a[i]/r*nm),nm=a[i]/r*nm,ans=(ans%nm+nm)%nm;
}
return ans;
}
int main()
{
scanf("%lld",&t);
while(t--)
{
scanf("%lld%lld",&n,&m);
memset(ch,0,sizeof(ch));
root=0,cnt=0;build();
for(long long i=1;i<=n;i++)scanf("%lld",&s[i]),b[i]=s[i];
for(long long i=1;i<=n;i++)scanf("%lld",&a[i]);
for(long long i=1;i<=n;i++)scanf("%lld",&z[i]);
for(long long i=1;i<=m;i++)
{
scanf("%lld",&x);
insert(root,x);
}
for(long long i=1;i<=n;i++)
{
c[i]=pre(b[i]);
if(c[i]==(long long)-1999999999999)c[i]=nxt(c[i]);
del(root,c[i]);
insert(root,z[i]);
}
long long ans=excrt();
if(ans==0)
for(long long i=1;i<=n;i++)
ans=max(ans,(s[i]-1)/c[i]+1);
printf("%lld\n",ans);
}
return 0;
}
例题
P5091 【模板】扩展欧拉定理
根据扩展欧拉定理,本质上就是需要求出次数除以模数的欧拉函数值的余数。我们可以逐位读入,利用余数的可乘性和可加性,每次将当前余数乘以
求出余数后,直接使用快速幂解决。
注意当
#include <bits/stdc++.h>
using namespace std;
long long a,m,n,now,flag,mod;
char b;
long long power(long long a,long long p)
{
long long ans=1,x=a;
while(p)
{
if(p&1)ans=ans*x%mod;
p>>=1;
x=x*x%mod;
}
return ans;
}
int main()
{
scanf("%lld%lld",&a,&m);
mod=n=m;
for(long long i=2;i*i<=m;i++)
if(m%i==0)
{
n=n/i*(i-1);
while(m%i==0)m/=i;
}
if(m!=1)n=n/m*(m-1);
while(b<'0'||b>'9')b=getchar();
while(b>='0'&&b<='9')
{
long long t=(now*10+b-'0')%n;
if(t<now*10+b-'0')flag=1;
now=t;
b=getchar();
}
if(flag)printf("%lld",power(a,now+n));
else printf("%lld",power(a,now));
return 0;
}
例题
P4139 上帝与集合的正确用法
实际上,题目就是要求以下式子的值:
由于幂的层数可以看作无限大,所以
很显然,中间那个
当模数为
#include <bits/stdc++.h>
using namespace std;
long long t,n,pri[1000010],phi[10000010],cnt,ans;
bool a[10000010];
long long power(long long a,long long p,long long mod)
{
long long x=a,ans=1;
while(p)
{
if(p&1)ans=ans*x%mod;
p>>=1;
x=x*x%mod;
}
return ans;
}
void linear(long long n)
{
a[1]=1;phi[1]=1;
for(long long i=2;i<=n;i++)
{
if(!a[i])pri[++cnt]=i,phi[i]=i-1;
for(long long j=1;j<=cnt&&i*pri[j]<=n;j++)
{
a[i*pri[j]]=1;
phi[i*pri[j]]=phi[i]*(pri[j]-1);
if(i%pri[j]==0)
{
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
}
}
}
long long oula(long long now)
{
if(now==1)return 0;
else return power(2,oula(phi[now])%now+phi[now],now);
}
int main()
{
scanf("%lld",&t);
linear(10000008);
while(t--)
{
scanf("%lld",&n);
printf("%lld\n",oula(n));
}
return 0;
}
例题
P3846 [TJOI2007] 可爱的质数/【模板】BSGS
bsgs 模板题,不多赘述。
#include <bits/stdc++.h>
using namespace std;
long long p,b,n,ans;
unordered_map<long long,long long>h;
long long power(long long a,long long p,long long mod)
{
long long ans=1,x=a;
while(p)
{
if(p%2==1)ans=ans*x%mod;
p/=2;
x=x*x%mod;
}
return ans;
}
long long bsgs(long long a,long long b,long long mod)
{
h.clear();
long long t=sqrt(mod)+1,now=1,inc=power(a,t,mod);
if(a==0)
{
if(b==0)return 1;
else return -1;
}
for(int i=0;i<=t-1;i++)h[now*b%mod]=i+1,now=now*a%mod;
now=1;
for(int i=1;i<=t;i++)
{
now=now*inc%mod;
if(h.count(now)>0)return i*t-h[now]+1;
}
return -1;
}
int main()
{
scanf("%lld%lld%lld",&p,&b,&n);
ans=bsgs(b,n,p);
if(ans==-1)printf("no solution");
else printf("%lld\n",ans);
return 0;
}
例题
P4195 【模板】扩展 BSGS/exBSGS
exbsgs 模板题,不多赘述。
注意此题数据较强,需要大量特判和卡常技巧,写起来不是很容易。
#include <bits/stdc++.h>
using namespace std;
long long a,p,b,x,y;
map<long long,long long>h;
inline long long read()
{
long long x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
long long gcd(long long a,long long b)
{
if(b==0)return a;
else return gcd(b,a%b);
}
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
long long r=exgcd(b,a%b,x,y),d=x;
x=y,y=d-a/b*y;
return r;
}
long long power(long long a,long long p,long long mod)
{
long long ans=1,x=a;
while(p)
{
if(p&1)ans=ans*x%mod;
p>>=1;
x=x*x%mod;
}
return ans;
}
long long bsgs(long long a,long long b,long long mod)
{
h.clear();
long long t=sqrt(mod)+1,now=1,inc=power(a,t,mod);
if(a==0)
{
if(b==0)return 1;
else return -1;
}
for(int i=0;i<=t-1;i++)h[now*b%mod]=i+1,now=now*a%mod;
now=1;
for(int i=1;i<=t;i++)
{
now=now*inc%mod;
if(h.count(now)>0)return i*t-h[now]+1;
}
return -1;
}
long long exbsgs(long long a,long long b)
{
long long d=gcd(a,p),cnt=0,sum=1,ans=0;
a%=p,b%=p;
if(b==1||p==1)return 0;
if(a==0)
{
if(b==0)return 1;
else return -1;
}
while(d!=1)
{
if(b%d!=0)return -1;
cnt++,p/=d,sum=sum*(a/d)%p,b/=d;
d=gcd(a,p);
if(sum==b)return cnt;
}
exgcd(sum%p,p,x,y);
x=(x%p+p)%p;
ans=bsgs(a,x*b%p,p);
if(ans==-1)return -1;
else return ans+cnt;
}
int main()
{
a=read(),p=read(),b=read();
while(1)
{
if(a==0&&b==0&&p==0)break;
long long ans=exbsgs(a,b);
if(ans==-1)printf("No Solution\n");
else printf("%lld\n",ans);
a=read(),p=read(),b=read();
}
return 0;
}
例题
P2480 [SDOI2010] 古代猪文
基础数论大杂烩。根据题意,题目就是给定整数
因为
关键就转化为了求
把
我们可以考虑分别求出
然后,我们构造出一个线性同余方程组:
直接使用中国剩余定理求出
最后,用快速幂求出
注意,当
#include <bits/stdc++.h>
using namespace std;
long long n,g,x,y,jc[50000],inv[50000],a[5]={0,2,3,4679,35617},b[5],m[5],t[5];
const long long mod=999911659;
long long mul(long long a,long long p,long long m)
{
long long x=a,ans=0;
while(p)
{
if(p%2==1)ans=(ans+x)%m;
p/=2;
x=(x+x)%m;
}
return ans;
}
long long power(long long a,long long p,long long m)
{
long long ans=1,x=a;
while(p)
{
if(p&1)ans=ans*x%m;
p>>=1;
x=x*x%m;
}
return ans;
}
long long exgcd(long long a,long long b,long long &x,long long &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
long long r=exgcd(b,a%b,x,y);
long long d=x;
x=y;
y=d-a/b*y;
return r;
}
void cal(long long m)
{
jc[0]=1,inv[0]=1;
for(int i=1;i<=m-1;i++)jc[i]=jc[i-1]*i%m;
inv[m-1]=power(jc[m-1],m-2,m);
for(int i=m-2;i>=1;i--)inv[i]=inv[i+1]*(i+1)%m;
}
long long get_c(long long n,long long k,long long m)
{
if(k>n)return 0;
return jc[n]*inv[n-k]%m*inv[k]%m;
}
long long lucas(long long n,long long k,long long m)
{
if(k==0)return 1;
return get_c(n%m,k%m,m)*lucas(n/m,k/m,m)%m;
}
long long solve(long long m)
{
long long r=0;
cal(m);
for(long long i=1;i*i<=n;i++)
if(n%i==0)
{
if(i!=n/i)r=(r+lucas(n,i,m)+lucas(n,n/i,m))%m;
else r=(r+lucas(n,i,m))%m;
}
return r;
}
long long excrt()
{
long long nm=a[1],ans=b[1];
for(int i=2;i<=4;i++)
{
long long c=((b[i]-ans)%a[i]+a[i])%a[i];
long long q=exgcd(nm,a[i],x,y);
if(c%q!=0)return -1;
x=mul(x,c/q,a[i]/q);
ans+=x*nm,nm*=(a[i]/q),ans=(ans%nm+nm)%nm;
}
return (ans%nm+nm)%nm;
}
int main()
{
scanf("%lld%lld",&n,&g);
if(g==mod)
{
printf("0");
return 0;
}
for(int i=1;i<=4;i++)b[i]=solve(a[i]);
printf("%lld",power(g,excrt(),mod));
return 0;
}
后记
这篇成功取代 【7】Tarjan学习笔记,共
upd on
数论这一块,套路很多,只能说熟能生巧吧。
但是最近几年提高组和国赛好像没怎么考数论。