部落格處女作:中國剩餘定理與擴充套件中國剩餘定理
各位好啊,這裡是蒟蒻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時的逆元。
所謂中國剩餘定理,就是指
接下來我們來證明為什麼如此。
首先因為Mi^-1是Mi的逆元,所以
因為Mi是包含mj的(j!=i),所以明顯
既然如此,就有
後面那個t*M是一個通解,因為M是所有m的乘積所以肯定滿足
那麼我們得到了中國剩餘定理:(所有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