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

正题

      中国剩余定理要求的就是一个同时满足下列式子每一个同余方程的最小的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. $$

      可以知道答案就是\small \sum_{i=1}^n b_i*inv(sb_i,b_i)*sb_i,可以发现对于任何一个i构造的值来说,都满足第i个方程,而且对其他的n-1个方程没有影响.

#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是最好的选择

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值