扩展中国剩余定理
用途和介绍
用于求解线性方程组:
\(\begin{cases}x\equiv a1(\bmod m1)
\\x\equiv a2(\bmod m2)
\\......
\\x\equiv an(\bmod mn)
\end{cases}\)
数学归纳法:设\(x\)为前\(k-1\)个同余方程的一个特解,则通解为\(x+t\times M\),其中\(M=lcm(m[1],m[2],m[3],......,m[k-1])\),那么\(x+t\times M=a[k](\bmod m[k])\),只要有\(k\)存在,那么第\(k\)个方程亦有解。
观察这个式子,把\(x\)移项,使用扩展欧几里得算法,不要忘记裴蜀定理判无解。
扩展中国剩余定理的优势在于消除了中国剩余定理\(m1,m2,...,mn\)互质的要求。
一些例子
P4777 【模板】扩展中国剩余定理(EXCRT)
板子,代码里的所有有关数组名称都和上文一样
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100010;
int x,y,n;
int a[N],m[N];
int exgcd(int a,int b){
if(b==0){
x=1,y=0;
return a;
}
else{
int as=exgcd(b,a%b);
int xx=x,yy=y;
x=yy,y=xx-(a/b)*yy;
return as;
}
}
int qmul(int af,int bf,int p){
int aa=0,x=af;
//cout<<"yes"<<endl;
while(bf){
if(bf&1){
aa+=x;
aa%=p;
}
x*=2;
x%=p;
bf/=2;
}
//cout<<"ok"<<endl;
return aa;
}
void excrt(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>m[i]>>a[i];
}
int ans=a[1],lcm=m[1];
for(int i=2;i<=n;i++){
int aa=lcm,bb=m[i],cc=a[i]-ans;
cc=(cc%m[i]+m[i])%m[i];
int d=exgcd(aa,bb);
if(cc%d){
return;//这题保证一定有解,这一行是裴蜀定理判无解
}
ans=ans+lcm*qmul(x,cc/d,m[i]);//qmul是龟速乘,直接乘会爆
lcm/=d,lcm*=m[i];
ans=(ans%lcm+lcm)%lcm;
}
cout<<ans;
}
signed main(){
excrt();
return 0;
}
CF687B Remainders Game
其实是结论题,如果我们知道一个数\(x\)除以\(c_1,c_2,...,c_n\)的余数,相当于是给了一个线性同余方程组。
通过扩展中国剩余定理可以解出一个特解\(x'\),那么\(x=x'+lcm(c_1,c_2,...,c_n)\),相当于可以得到任意数除以\(lcm(c_1,c_2,...,c_n)\)的余数。
如果\(k\)是这个\(lcm\)的因数就可以得到任意数除以\(k\)的余数,否则不行。
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int n,k;
cin>>n>>k;
int p=1;
for(int i=1;i<=n;i++){
int c;
cin>>c;
p=(p*c)/__gcd(p,c);
p%=k;
}
if(p%k==0) cout<<"Yes";
else cout<<"No";
return 0;
}
P2480 [SDOI2010] 古代猪文
一道大综合题。
首先写出题目要求的式子:\(g^{\sum_{d|n}C_{n}^{d}}\)\(\bmod 999911659\)。
次数太高,自然想到欧拉定理降次数,由欧拉定理:\(a^b\equiv a^{b\bmod \varphi (p)}(\bmod p)\)
由于\(999911659\)是质数,有\(\varphi(999911659)=999911658\),那么只要求\(g^{\sum_{d|n}C_{n}^{d}\bmod 999911658}\)\(\bmod 999911659\)。
计算瓶颈在于\(\sum_{d|n}C_{n}^{d}\bmod 999911658\),首先分解质因数。然后发现\(n\)很大,不好处理组合数的除法,那么使用卢卡斯定理。
卢卡斯定理:
若\(p\)为质数,有:
但是,\(999911658\)并不是质数,只能先把它质因数分解,发现只分解出\(4\)个质因子,\(999911658=2\times 3\times 4679\times 35617\)。
如果我们知道\(C_{n}^{d}\)分别除以这四个质因数的值,相当于是知道了四个同余式,那么这就是一个把\(C_{n}^{d}\)看成未知数解出这个线性同余方程组即可。
当然因为线性同余方程组里的余数都是质数,使用中国剩余定理也可以(调不出来了代码换成了中国剩余定理)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100010;
int x,y,n,p,g;
int a[N],m[5]={0,2,3,4679,35617};
int pre[N];
void prev(){
pre[0]=1;
for(int i=1;i<=100000;i++){
pre[i]=(pre[i-1]*i);
pre[i]%=p;
}
}
int qpow(int aa,int bb){
int vl=1,hl=aa;
hl%=p;
while(bb){
if(bb%2){
vl*=hl;
vl%=p;
}
hl*=hl;
hl%=p;
bb/=2;
}
return vl;
}
int C(int aa,int bb){
if(aa<bb) return 0;
int ans=pre[aa];
ans*=qpow(pre[aa-bb],p-2);
ans%=p;
ans*=qpow(pre[bb],p-2);
ans%=p;
return ans;
}
int lucas(int aa,int bb){
if(bb==0) return 1;
int val=C(aa%p,bb%p);
val*=lucas(aa/p,bb/p);
val%=p;
return val;
}
int exgcd(int a,int b){
if(b==0){
x=1,y=0;
return a;
}
else{
int as=exgcd(b,a%b);
int xx=x,yy=y;
x=yy,y=xx-(a/b)*yy;
return as;
}
}
int add(int pla){
//cout<<n<<" "<<pla<<" "<<p<<" "<<C(n,pla)<<" "<<lucas(n,pla)<<"\n";
return lucas(n,pla)%p;
}
void excrt(){
cin>>n>>g;
for(int i=1;i<=4;i++){
p=m[i];
prev();
for(int j=1;j*j<=n;j++){
if(!(n%j)){
//cout<<"in"<<"\n";
a[i]+=add(j);
a[i]%=p;
if((n/j)==j) continue;
a[i]+=add(n/j);
a[i]%=p;
}
}
//cout<<a[i]<<" "<<m[i]<<"\n";
}
int ans=0;
for(int i=1;i<=4;i++){
p=m[i];
ans=(ans+a[i]*(999911658/m[i])%999911658*qpow(999911658/m[i],m[i]-2));
}
p=999911659;
if(g%p==0){
cout<<0;
return;
}
cout<<qpow(g,ans);
}
signed main(){
excrt();
return 0;
}
P4774 [NOI2018] 屠龙勇士
其实是典题,但是和数组结构结合了一下还是有训练价值的。
使用平衡树(好像可以用\(multiset\))找出每一轮使用的剑(支持插入,删除,找前驱,最小值),然后就是扩展中国剩余定理的板子。
还未更新完。