学习笔记第十节:中国剩余定理

版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢 https://blog.csdn.net/Deep_Kevin/article/details/82080974

正题

      中国剩余定理要求的就是一个同时满足下列式子每一个同余方程的最小的x。(保证互质)

                                                                 \left\{ \begin{aligned} x \equiv b_1&&&&mod&&a_1\\ x \equiv b_2&&&&mod&&a_2\\ ...\\ x \equiv b_n&&&&mod&&a_n \end{aligned} \right. $$

      我们需要显而易见的知道一些东西。

      如果我们可以找出一个k_i,使得它是除了a_i的其他的积的倍数,并且mod\,\,\,\, a_i后为b_i

      那么\sum_{i=1}^n k_i就是我们的答案。

      现在我们来讨论,对于其中的i,我们怎么求出一个k_i.

      首先 

                                                                            k_i = \prod_{other}*x.

      并且

                                                                            k_i\equiv b_i \,\,mod\,\,a_i

      所以

                                                                    \prod_{other}*x\equiv b_i \,\,\,mod\,\,\,a_i

      把乘积写成q,把mod写成加上a_i的y倍,可以变成

                                                                     q*x+a_i*y=b       

      很明显就可以发现,这是一个 exgcd的形式,又因为q和a_i是互质的,所以gcd是1.

      我们很清楚一个东西,被除数/除数=商......余数,被除数乘上(除以)x,商和余数也会乘上(除以)x。

      所以,我们先求出

                                                                     q*x+a_i*y=1

      然后我们把x乘上b_i就可以求出来了,最后再mod 一下最小公倍数。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

int n;
int a[20],b[20];

void exgcd(int a,int b,int &x,int&y){
	if(b==0) {x=1,y=0;return ;}
	exgcd(b,a%b,y,x);
	y-=(a/b)*x;
}

int get_ans(){
	int m=1;
	int tot=0;
	int x,y;
	for(int i=1;i<=n;i++) m*=a[i];//先求全部的累乘。
	for(int i=1;i<=n;i++){
		int q=m/a[i];//求出q
		exgcd(q,a[i],x,y);
		tot=(tot+(x*b[i]*q)%m)%m;
	}
	return tot;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d %d",&a[i],&b[i]);
	printf("%d",get_ans());
}

那么,如果模数之间不是互质的呢?

下面请有请

扩展中国剩余定理

       扩展中国剩余定理是万能的。

       我们想一想,怎么求两条式子的公共解。

       x=a_1*k_1+b_1\\x=a_2*k_2+b_2

      那么a_1*k_1+b_1=a_2*k_2+b_2

      变换一下,就可以知道a_1*k_1-a_2*k_2=b_2-b_1

      再把负号放入k_2,就可以知道a_1*k_1+a_2*(-k_2)=b_2-b_1

      还不赶快欢迎扩展欧几里得。。。

      我们用exgcd求出正确的k_1,然后得出一个关于这两条式子最小的通解x。

      现在我们就可以知道了,答案必须是x+lcm(a_1,a_2)

      所以我们得出了一条新的式子ans\equiv x\,\,\,mod\,\,lcm(a_1,a_2)

      那么就完成了合并两条成一条的操作。

      n-1次合并即可。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

int n;
long long a[100010],b[100010];

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 p=exgcd(b,a%b,x,y);
	long long t=y;
	y=x-(a/b)*y;
	x=t;
	return p;
}

long long get_ans(){
	long long A=a[1],B=b[1],d;
	long long x,y,t;
	long long last;
	for(int i=2;i<=n;i++){
		if(a[i]==a[i-1] && b[i]==b[i-1]) continue;
		d=exgcd(A,a[i],x,y);//求两条同余方程的公共解
		if((b[i]-B)%d) return -1; //不整除说明肯定无解 
		x*=(b[i]-B)/d;t=(a[i]/d);x=(x%t+t)%t;//求x,因为gcd是d,所以应该除以d乘(b2-b1) 
		last=A;A=A/d*a[i];//新的模数就是lcm 
		B+=(last%A)*x%A;B=(B+A)%A;//用B+A*x(新解)%lcm(a1,a1)作为新的同余方程 
	}
	B=(B%A+A)%A;
	return B;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld %lld",&a[i],&b[i]);
	printf("%lld",get_ans());
}

面对那些恶心的洛谷评测,选择__int128是最好的选择

猜你喜欢

转载自blog.csdn.net/Deep_Kevin/article/details/82080974