1. 程式人生 > >部落格處女作:中國剩餘定理與擴充套件中國剩餘定理

部落格處女作:中國剩餘定理與擴充套件中國剩餘定理

各位好啊,這裡是蒟蒻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