浅谈中国剩余定理
我们先来看这样一道题目:
树王种了一棵treap,她现在决定把这棵treap改造为一棵无旋多叉triep,于是她摘下了treap的所有节点,发现如果她把节点3个3个一打包,会剩下2个节点。如果她把节点5个5个一打包,会剩下3个节点,如果把节点7个7个一打包,会剩下2个节点,求这棵treap最少有多少节点?
简化后题目就是,已知x%3=2,,x%5=3,x%7=2,求x最小值?
如何解决呢?我们设k1,k2,k3满足 k1%3=1,k1%5=0,k1%7=0,k2%5=1,k2%3=0,k2%7=0.k3%7=1.,k3%3=0,k3%5=0.
那么 k1*2+k2*3+k3*2一定是满足答案的一个解。(网上说容易意会,容易意会个鬼啊)
证明: 设x=k1*2+k2*3+k3*2。 则x%3=(k1*2+k2*3+k3*2)%3=(k1*2%3+k2*3%3+k3*2%3)%3=(2+0+0)%3=2;
其他两组解同理。
那么如何求k1,k2,k3?
我们求出LCM(3,5,7)=105,设x1=LCM(3,5,7)/3=35,x2==LCM(3,5,7)/5=21,x3==LCM(3,5,7)/7=15.
那么设k1=n1*x1,k2=n2*x2,k3=n3*x3.
k1%3=1,k2%5=1,k3%7=3,可转换为 n1*x1%3=1,n2*x2%5=1,n3*x3%7=1.
然后可以用扩展欧几里得求出n1,n2,n3.进而得到 k1,k2,k3.
最后便可得到答案,而题目的通解可表示为这个数每次都加上3,5,7的最小公倍数。
好了,现在步入正题(你没看错现在我才开始讲正经的,刚才那一堆都是铺垫)
已知 x≡a[1](mod)y[1],x≡a[2](mod)y[2],x≡a[3](mod)y[3].........x≡a[n](mod)y[n].
其中 y[1],y[2],y[3]......y[n]两两互质,求x
其算法流程如下。
1.计算LCM(y1,y2......yn)。
2.从1->n,计算f[i]=LCM(y[1[,y[2],....y[n])/y[i].
3.使用扩展欧几里得定理,对同余式b[i]*f[i]%y[i]=1,计算出b[i],进而得出k[i]=b[i]*f[i].
4.从1->n,计算x=k[1]*a[1]+k[2]*a[2]+...........k[n]*a[n].
5.对x模lcm,得到答案。
接下来给出代码:
#include <bits/stdc++.h> #define maxn 105 using namespace std; int ex_gcd(int a,int b,int &x,int &y)//扩展欧几里得定理,解ax+by=c。 { if(b==0) { x=1; y=0; return a; } int ans=ex_gcd(b,a%b,x,y); int temp=x; x=y; y=temp-(a/b)*x; return ans; } int a[maxn],b[maxn],n,f[maxn]; int solve() { int ans=0; int lcm=1; for(int i=1;i<=n;i++) { lcm*=a[i];//两两互质,直接乘了 } for(int i=1;i<=n;i++) { f[i]=lcm/a[i]; } for(int i=1;i<=n;i++) { int x,y,g; g=ex_gcd(f[i],a[i],x,y); x=(x%a[i]+a[i])%a[i];//因为扩展欧几里得求出的解可能有很多组,且可能为负,我们这步求出了一个比a[i]小且大于0的解。 ans=(ans+x*f[i]*b[i])%lcm; } return ans; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d%d",&a[i],&b[i]);//ans%a[i]=b[i]. } int ans=solve(); printf("%d\n",ans); }
接下来我们谈谈扩展中国剩余定理
今天才被扩展欧拉定理gank的我来学习扩展中国剩余定理了
已知 x≡a[1](mod)y[1],x≡a[2](mod)y[2],x≡a[3](mod)y[3].........x≡a[n](mod)y[n],求x。
现在y[1],y[2]........y[n]不互质了,一般情况的中国剩余定理不适用了,这怎么搞?
考虑 两个方程,分别为x≡a[1](mod)y[1],x≡a[2](mod)y[2].
则有x=y[1]*n1+a[1],x=y[2]*n2+a[2].
合并得 y[1]*n1+a[1]=y[2]*n2+a[2].化简: y[1]*n1-y[2]*n2=a[2]-a[1].
使用扩展欧几里得定理 解除最小正整数解 n1,设k=y[1]*n1+a[1].
那么存在通解x=k+t*lcm(y[1],y[2]).有 x≡k(mod)lcm(y[1],y[2])。
一路合并下去就ok了。
接下来给出例题 POJ2891代码
传送门:Strange Way to Express Integers
#include <cstdio> #define maxn 100005 using namespace std; typedef long long ll; ll ex_gcd(ll a,ll b,ll &x,ll &y)//扩展欧几里得定理,解ax+by=c。 { if(b==0) { x=1; y=0; return a; } ll ans=ex_gcd(b,a%b,x,y); ll temp=x; x=y; y=temp-(a/b)*x; return ans; } ll a[maxn],b[maxn],n,f[maxn]; ll China() { ll mod=a[1],reminder=b[1];//模数为mod,余数为reminder。 for(int i=2;i<=n;i++) { ll x,y,g,temp; g=ex_gcd(mod,a[i],x,y); temp=b[i]-reminder; if(temp%g!=0) { return -1; } x=x*temp/g; ll t=a[i]/g; x=(x%t+t)%t; reminder=x*mod+reminder; mod=mod/g*a[i]; reminder%=mod; } reminder=(reminder%mod+mod)%mod; return reminder; } int main() { while(~scanf("%lld",&n)) { for(ll i=1;i<=n;i++) { scanf("%lld%lld",&a[i],&b[i]); } printf("%lld\n",China()); } }