博客处女作:中国剩余定理与扩展中国剩余定理

版权声明:这是gigo写的QAQ https://blog.csdn.net/qq_42835815/article/details/84898116

各位好啊,这里是蒟蒻gigo_64的第一篇博客,,这里我们开始啦。

本文需要读者知晓扩展欧几里得,如果不会请点击这个大佬的链接;https://blog.csdn.net/sslz_fsy/article/details/81566257

中国剩余定理是用来求解一个方程组的。这个方程组如下:

“图片”

怎么办呢,,我想求一个x怎么办呢,,

好我们引入中国剩余定理。

1.中国剩余定理

首先我们给出一个条件:所有mi互质。好像看起来没有什么用。

现在,我们定义一些量:

ai:如题。

mi:如题。

M:所有mi的乘积。

Mi:M/mi。(也就是除了mi之外其它m的乘积)

Mi^-1:Mi模mi时的逆元。

所谓中国剩余定理,就是指x=\sum^k_{i=1}a_iM_iM_i^{-1} mod M

接下来我们来证明为什么如此。

首先因为Mi^-1是Mi的逆元,所以M_iM_i^{-1}\equiv1(mod m_i)

因为Mi是包含mj的(j!=i),所以明显M_iM_i^{-1}\equiv0(modm_j)

既然如此,就有

x=\sum^k_{i=1}M_iM_i^{-1}+t*M

后面那个t*M是一个通解,因为M是所有m的乘积所以肯定满足

那么我们得到了中国剩余定理:x=(\sum^k_{i=1}a_iM_iM_i^{-1})modM(所有m互质)

其实我也不太明白为什么x从通解那个地方能转换到这里的定理。

上代码吧。

但我现在可以引入中国剩余定理·扩展

2.扩展中国剩余定理。

定义很简单,中国剩余定理的基础上加上了!!!m不一定互质!!!

这下会很麻烦,,因为m不一定互质会让之前推出剩余定理的过程一开始就不成立。

对此,我们可以使用这样一个技能。

假设你现在得到了满足前k个方程的通解x。

正如我刚刚所说,x的通解是加上了t*M的,可惜这个M是前k个m的最小公倍数,而不是所有m的。

我们设前k个方程的通解为ans,之前的M为lcm,下一个方程为x=a[k+1](mod m[k+1]);

那我们现在的目标是让x满足前面方程的前提下满足x=a[k+1](mod m[k+1]);

也就是说,要满足ans+t*lcm=a[k+1]+m[k+1]*y;

恒等变形得到t*lcm-y*m[k+1]=a[k+1]-ans;并且我把减号改成加好不会有问题因为y是没什么用的。

所以t*lcm+y*m[k+1]=a[k+1]-ans好像很眼熟,

这是个扩展欧几里得的模板样子的等式,lcm,m[k+1],a[k+1],ans都是已知。

直接上扩欧的模板exgcd(lcm,m[k+1])就可以得到t,并且在得到t的过程中顺带得到lcm与m[k+1]的最大公约数gcd

要注意一个事实,就是我们求到的t是满足左边=gcd而非a[k+1]-ans的。

那直接让t=t/gcd*a[k+1]-ans。因为t是整数,所以如果(a[k+1]-ans)%gcd!=0的话,这个方程组无解。

现在我们得到了真正的t,那新的ans=ans+t*lcm;

一定要在更新完ans后再更新lcm。lcm=lcm*m[i]/gcd;(两个数的最小公倍数就是两数相乘除以最大公约数嘛)

然后记得ans要取模不然极可能爆long long。ans=(ans%lcm+lcm)%lcm;

上代码,带注释,这里是洛谷4777,因为会爆longlong所以用快速幂分解处理了t*lcm。

#include<bits/stdc++.h>
using namespace std;
long long n;
long long a[100003],m[100003],M=1,ans,x,y,gcd;
void exgcd(long long a,long long b,long long &x,long long &y){
	if(!b){
		x=1,y=0;gcd=a;return;//就是扩欧模板,不知道的可以去sslz_fsy那里看模板ovo 
	}
	exgcd(b,a%b,y,x);y-=a/b*x;
}long long t;
void mul(long long a,long long b,long long p){
	long long ans=0;
	while(b){//将两个数相乘分解成很多个数*b的和,边加边%防止爆longlong 
		if(b&1)ans+=a,ans%=p;
		a=a*2%p;
		b>>=1;
	}//可以自己理解一下 
	t=ans;//看,这个才是真正的t 
}
int main(){
	cin>>n;
	for(register int i=1;i<=n;i++)scanf("%lld %lld",&m[i],&a[i]);
	long long lcm=m[1],ans=a[1];//第一个方程直接上 
	for(register int i=2;i<=n;i++){
		long long c=(a[i]-ans%m[i]+m[i])%m[i];//防止爆炸的模加模,c就是a[k+1]-ans 
		exgcd(lcm,m[i],x,y);//求出的x是初步的假t ,顺带求出gcd 
		mul(x,c/gcd,m[i]/gcd);//意思是x*(c/gcd)%m[i]/gcd 
		ans=t*lcm;//经过mul得到的t就是真正的t啦,直接加 
		lcm=lcm*m[i]/gcd;//更新lcm 
		ans=(ans%lcm+lcm)%lcm;//防止爆炸的模加模 
	}
	cout<<ans;//万恶的cout 
	return 0;
}

我的第一篇博客非常非常非常啰嗦。希望以后自己能看得懂哈哈哈

请多指教啦ovo

猜你喜欢

转载自blog.csdn.net/qq_42835815/article/details/84898116
今日推荐