数论(3) | 中国剩余定理与拓展中国剩余定理

1 中国剩余定理crt(洛谷P3868)

已知同余方程组:

 

 求x的最小非负整数解。

推荐一篇超棒的博客 -> https://www.cnblogs.com/MashiroSky/p/5918158.html

大多数题解都是上来就一堆公式,看的很累。这个大佬的博客上来先演示了一遍中国剩余定理的运算过程,再上公式就清楚了:

在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?”

解决这个问题需要三步:

1.找到三个数:从3和5的公倍数中找出被7除余1的最小数15,从3和7的公倍数中找出被5除余1 的最小数21,最后从5和7的公倍数中找出除3余1的最小数70。

2.用15乘以2(七七数之剩二),用21乘以3(五五数之剩三),同理,用70乘以2(三三数之剩二),然后相加15*2+21*3+70*2=233。

3.用233除以3,5,7三个数的最小公倍数105,得到余数23,即233%105=23。这个余数23就是符合条件的最小数。

这是理解crt的关键。其实读到这里再自己思考一下就可以理解其原理了。还有不理解的可以参考博客原文,这里也就不再复制粘贴了。下面直接给出公式:

其中

 前提:mi必须保证互质!(比如现在我们要找一个模6余2,模9余5的数字,根据中国剩余定理,我们要先在9的倍数中找到一个模6余1的数,这显然是不存在的。但模6余2,模9余5的数字还是有的,比如15

那如果不互质怎么办呢?一会儿会讲excrt。先把crt的代码给出来。

 1 #include <bits/stdc++.h>//中国剩余定理
 2 using namespace std;
 3 typedef long long LL;
 4 LL mul(LL x,LL y,LL p){
 5     LL ans=0;
 6     while (y){
 7         if (y&1) ans=(ans+x)%p;
 8         y>>=1;
 9         x=(x*2)%p;
10     }
11     return ans;
12 }
13 LL exgcd(LL a,LL b,LL &x,LL &y){
14     if (b==0){
15         x=1;y=0;
16         return a;
17     }
18     LL r=exgcd(b,a%b,x,y);
19     LL t=x;
20     x=y;
21     y=t-(a/b)*y;
22     return r;
23 }
24 int main(){
25     int n;
26     long long a[11],b[11];
27     scanf("%d",&n);
28     LL M=1,ans=0,x,y;
29     for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
30     for (int i=1;i<=n;i++) scanf("%lld",&b[i]),M*=b[i];
31     for (int i=1;i<=n;i++) a[i]=(a[i]%b[i]+b[i])%b[i];
32     for (int i=1;i<=n;i++){
33         LL t=M/b[i];
34         exgcd(t,b[i],x,y);
35         x=(x%b[i]+b[i])%b[i];
36         ans=(ans+mul(mul(t,x,M),a[i],M))%M;
37     }
38     printf("%lld",ans);
39     return 0;
40 }

2 中国剩余定理excrt(洛谷P4777)

当mi不保证互质的时候,我们不能直接使用中国剩余定理,而是首先要进行两两合并。

比如现在m1,m2不互质,我们要合并下面两个式子: 

 联立,得:

因为a>0,m>0,我们要找到一个x最小的正整数特解x’,就要求出x1(或者x2)的最小正整数解,exgcd一下就可以了。最终式子可以合并为

(接上面的例子,我们要找一个模6余2,模9余5的数字,变换式子得6x1+9x2=1,exgcd求得x特解14。又因6和9的公倍数是18,两个式子可以合并为x≡14(mod 18),接下来找x只需要在14+18k里面找就行了。)

贴代码!

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 typedef long long LL;
 4 LL exgcd(LL a,LL b,LL &x,LL &y){
 5     if (b==0){
 6         x=1;y=0;
 7         return a;
 8     } 
 9     LL r=exgcd(b,a%b,x,y);
10     LL t=x;
11     x=y;
12     y=t-(a/b)*y;
13     return r;
14 }
15 LL mul(LL a,LL b,LL m){
16     LL x=b,y=0;
17     while (a){
18         if (a&1) y=(y+x)%m;
19         x=(x+x)%m;
20         a>>=1;
21     }
22     return y;
23 }
24 int main(){
25     LL a[100001],b[100001],n,sum=0;
26     scanf("%lld",&n); 
27     for (int i=1;i<=n;i++){
28         scanf("%lld%lld",&a[i],&b[i]);
29     }
30     LL M=a[1],ans=b[1];
31     for (int i=2;i<=n;i++){//已知k-1个式子的特解ans和最小公倍数M,现要加上第k个式子。ans+t*M=b[j] (mod a[i]) 
32         LL x,y,c,g,t;
33         c=(b[i]-ans%a[i]+a[i])%a[i];//防止b[i]-ans为负数 
34         g=exgcd(a[i],M,x,y);//拓展欧几里得a[i]*x+M*y=c 
35         if (c%g) {
36             cout<<"Impossible";
37             return 0;
38         }
39         t=a[i]/g;
40         y=mul(c/g,y,t);//放大 
41         y=(y%t+t)%t;//求最小正整数解 
42         ans=ans+mul(y,M,M*(a[i]/g));
43         M=M*(a[i]/g);
44         ans=(ans%M+M)%M;//更新ans与M 
45     }
46     cout<<ans;
47     return 0;
48 }
posted @ 2021-07-04 10:09  奇思妙想张霁羊  阅读(257)  评论(0)    收藏  举报