[數學]求解一類方程的方法 博文筆記&&擴展歐幾里得、中國剩余定理學習筆記。


原文傳送:https://www.luogu.com.cn/blog/1239004072Angel/post-shuo-xue-qiu-xie-yi-lei-fang-cheng-di-fang-fa

  不推薦只看原文進行學習。

 

裴蜀定理 (ax+by的最小正值還能整除a,b, 該最小正值為gcd(a,b))。

 方程ax+by=c (a,b,c為整數)有整數解的充要條件為gcd(a,b) | c 原博客只證明了充分性,這里證明必要性:

  若方程有整數解x1,y1,則c=ax1+by1。因為ax1+by1能被 gcd(a,b)整除,則c必然能被gcd(a,b)整除,否則方程無解。(一個方程變形罷了)

 

擴展歐幾里得算法:

  總的來說,擴展歐幾里得用來求不定方程ax+by=c的整數解或線性同余方程ax≡c mod b。利用歐幾里得算法框架遞歸到b=0后返回得到答案。

    推到a′x+b′y=b′x'+(a′modb′)y' ① 時(注意等號兩邊的a'是同一個數(b'同理),但x和x'不是同一個數(y和y'同理),原博客沒有體現出來),可令a''=b',b''=a′modb′,那么方程能像這樣再一直等於下去,直到 某個b'...'  (這里省略號是若干'的意思)為0 (a,b的變化就是歐幾里得算法的過程),那么此時方程為a'...' x = gcd(a'...',0)=a'...',得到x'...'=1,y'...'=任意整數 的解。為了方便,可以取y'...'=0。加上 下文已知, 后面的方程的解可推出前面方程的解,那么一開始的方程就解出來了。

    一個更清楚的口胡解釋:方程a′x+b′y=gcd(a',b')和b′x'+(a′modb′)y'=gcd(b′,a′modb′) 都有解(裴蜀定理)(也許用表示聯立的大括號表示更直白),同時 gcd(a',b')=gcd(b′,a′modb′) 。解出方程b′x'+(a′modb′)y'=gcd(b′,a′modb′) 后,方程a′x+b′y=gcd(a',b')=gcd(b′,a′modb′)=b′x'+(a′modb′)y'  (此時x' 、y‘ 確定,b′x'+(a′modb′)y'為一個定值),這時方程最右邊可變形為:a′y’+b′(x‘−b′/a′  ​ * y’),即​a′x+b′y=a′y’+b′(x‘−b′/a′  ​ * y’),這里面a',b',x',y'都已知,那么取x=y',y=x‘−  b′/a′  ​* y’就是原方程的一個解。這時可以知道,要求當前不定方程ax+by=gcd(a,b)的解,可看它對應的下一個方程bx'+(a%b)y'=gcd(b,a%b)的解,而這”下一個方程“的解,又可以看它的下一個方程即“下下個方程‘’…直到最后看的方程的第二個系數為零,得解,再按順序返回到第一個方程,就得到了答案。(遞歸的依據就是gcd(a,b)=gcd(b,a%b),故叫擴展歐幾里得,還能順便求出最大公因數)

  

  對於方程ax+by=c  (gcd(a,b)|c) 設k=c/gcd(a,b)  求出ax+by=gcd(a,b)的解x,y后將x,y都*k 即為ax+by=c 的一組特解。

  代碼:

 1 int exgcd(int a,int b,int &x,int &y)
 2 //調用函數完成后傳址&x,&y的變量的值就為關於當前函數a,b的值的ax+by=gcd(a,b)的解(x,y) 
 3 {
 4     if(!b)
 5     {
 6         x=1;y=0;
 7         return a;
 8     }
 9     int g=exgcd(b,a%b,y,x);//反着傳址,省去后文交換x,y的值 
10     y-=a/b*x;
11     return g;
12 }
13 
14 int main()//求解不定方程ax+by=c
15 {
16     int a=read(),b=read(),c=read(),x,y;
17     int g=exgcd(a,b,x,y),e=c%g;//按址傳遞形參。
18     //不寫按址傳遞形參的話,可以寫結構體或全局變量  
19     if(e)//無解 
20     {
21         cout<<"NONE"; 
22         return 0; 
23     }
24     x*=c/g;y*=c/g;//不要忘了c不一定是gcd(a,b) 
25     cout<<x<<endl<<y;
26     return 0;
27 }

    上面討論的情況只是a,b,c都大於0的情況。當a,b,c有小於0的情況時,特判一下:若是a或b小於0,則完全可以把負號去掉,最后再給x或y變下號就好;若是c小於0,那么c/g小於0,並不需要做什么多余操作;若a,b小於0,可等號兩邊都提出一個-1來,轉化成c小於0的情況;若a,b,c都小於0,則可等號兩邊都提出一個-1來,轉化成a,b,c大於0都情況。第一種情況相當於換元,故x或y最后要額外處理,后兩種情況是方程的變形,不會改變解。

    也可以不用這么麻煩。若會小於0,算法向下遞歸的正確性不變,只是遞歸到b=0時,a可能為負值,此時2種處理:

      1、算法返回a的絕對值,滿足exgcd返回值仍為gcd(a,b),令x=  a<0 ?-1 :1   (因為gcd一定是非負的)

      2、令x=1,算法返回a,exgcd返回值(設為g)的絕對值才是gcd,且此時x,y是滿足ax+by=g的一組解(ax+by=-gcd一樣有解,那么也可憑此遞歸下去,因為能遞歸下一層的依據就是構造了兩個等號右邊一樣(不管是gcd還是-gcd),左邊系數有關系、可變形為同樣形式的方程,這樣求出一個方程的解,另一個就可根據形式寫出解)。

  求出不定方程ax+by=c (a,b>0)的一組特解x0,y0后,他的所有解都可以表示為:x=x0+k*(b/gcd(a,b)),y=y0-k*(a/gcd(a,b)),k為整數。對於a,b有小於0的情況,根據上文來轉化為a,b大於0的情況,最后決定是否對x,y變號。

    簡單證明:不定方程ax+by=c (a,b>0)的解相對於x0,y0都可以表示成x=x0+u,y=y0+v的形式,且u和v異號(因為a,b>0)。設d=a,b的最小公倍數,則d=a*b / gcd(a,b)  。
      考慮x0緩慢增加(減少同理)以找到新的解,發現只有ax增加的量w為d的倍數時,by才能沒有在b、y都為整數的情況下抵消ax的增加量(w一定為a的倍數,否則x不為整數,w若不為b的倍數,則y不為整數。故若要求解為整數(不定方程的要求),w必為a,b的公倍數,即為d的倍數,w=kd,同時也說明相對於x0,y0的ax的變化量不為a,b的公倍數的解x,y是不存在的)ax變化了kd(增加還是減少要看k的符號)=k*  a*b / gcd(a,b),則x變化了k* b/gcd(a,b),by要向反方向變化kd ,得y變化了 -k * a/gcd(a,b) 。得證x=x0+k*(b/gcd(a,b)),y=y0-k*(a/gcd(a,b))一定還是原方程的解

  

  時間復雜度:與歐幾里得算法同級,上限都為logn,隨機數據下會更快。

    簡單證明:考慮a,b,a%b,b%(a%b)。a%b一定<b,猜想復雜度logn的話,考慮b%(a%b)與b/2的大小關系,而這又與a%b有關(因為b%(a%b) < a%b)。因為關鍵節點就是b/2,想到分兩種情況:

      a%b>  b/2,則b%(a%b)=b- a%b < b/2;

      a%b<  b/2,則b%(a%b)<a%b< b/2;

    發現gcd或exgcd函數每遞歸兩層,函數的第二參數的規模就減少至少一半,故復雜度上限級別為log b。

 

例題:洛谷P2421 [NOI2002] 荒島野人 https://www.luogu.com.cn/problem/P2421

  注意當ax+by=c的(a,b)=1時,通解x=x0+k*b的形式才成立,否則(a,b)不一定為1時通解應為標准形式x=x0+k*b/gcd(a,b),不要忘了一般情況下的通解公式的后部是有除以gcd的(要找全解,ax就要不斷變化a,b最小公倍數的量(a,b才是常數),x就變化b/gcd(a,b)的量)。y同理。

  還是容易在做完exgcd后忘記給解乘c/gcd(a,b),別忘了做完exgcd求出的是方程ax+by=gcd(a,b)的解,是不是ax+by=c的解還要再詳看(有可能還無解呢)。

  所謂的“想當然”最好自證一下再用。相似的概念並行思考時容易混淆,注意區分。

 

  注意:若想防止溢出,exgcd中不能簡單地對x,y模b/d,a/d。通解公式中x,y一個變化了,另一個也會變化。  

 

上文‘%’號未經特殊說明的話都指取余運算(畢竟我的語言環境是c++)。

 

中國剩余定理CRT(本文該模塊中bi是同余號右邊的數,ai是模數,ti是mi在模ai下的逆元):

  總的來說,用於求解模數互質系數為1的線性同余方程組。利用各模數與相應逆元構造了一個某模數下對應項結果為bi,其他項全是0的可行解。

  原文的“數學歸納”原來也是假設最后的解有多個再相減證明公倍數整除差值。

  證明及詳解(更清楚明確,還有個明確的性質):孫子定理  https://baike.baidu.com/item/%E5%AD%99%E5%AD%90%E5%AE%9A%E7%90%86/2841597?fromtitle=%E4%B8%AD%E5%9B%BD%E5%89%A9%E4%BD%99%E5%AE%9A%E7%90%86&fromid=11200132&fr=aladdin

  

   對於:   

 

    能在biti處模ai的原因:不管怎樣,mi必須要是除了自己(ai)的其他所有模數的乘積,以保證在被其他模數取模時結果為0,只剩下那個模數對應的項,才能保證正確性。而biti在模ai時才會看到,在最后答案中模ai的話,不會影響正確性,同時減少數值范圍(但不能對mi取模)

  

  代碼很簡朴,先求出模數積(O(n)),依次求出所有mi  (O(n)),ti (O(nlogn)),最后求出答案。總時間復雜度:O(nlogn)

  若方程組其中的同余方程的x前有系數,可用擴展歐幾里得解出該方程,轉化為系數為1的形式,最后總時間復雜度不變。

擴展中國剩余定理(模數不一定兩兩互質時求解同余方程組):

  用於求解模數不一定互質的線性同余方程組。通過聯立逐步合並方程組至一個方程。

  為方便理解中國剩余定理,有一個引理:對於兩個互質的正整數模數 n, m,給出兩個整數 a,b滿足 0≤a<n,0≤b<m,則在[0,nm)中存在且僅存在一個 同余方程組{ x modn =a,x mod m=b }的解 。

    證明很容易理解,n,m互質時同余方程組一定有多個解(由中國剩余定理百度百科可知)。對於同余方程組的兩個解x1,x2。作差得y ,y可同時被n 、m整除,即y為n,m的公倍數,那么在x1,x2他們的差距一定是n,m最小公倍數的倍數,x1,x2不相等的情況下最少也差lcm(n,m)(n,m互質的情況下lcm(n,m)即為nm),此時引理結論可得證。

    若模數不一定互質(對應擴展中國剩余定理),在方程組有解時(有解的條件下文再討論),也是會有多個解,對於這時的解 x1,x2,差y可同時被n 、m整除,仍為n,m的公倍數,x1,x2不相等的情況下最小差距還是n,m的最小公倍數。最后可知模數不一定互質時,若有解,則[0,lcm(n,m))中只會存在一個解 。

    由於 ai​ 之間可能不互質,故我們可能找不到某個 mi​ 在模 ai​ 意義下的逆元,就無法用CRT的公式求解。

 

  算法核心:變形等式,合並方程:

    

 

 

     兩個方程組是等價的,對第二個方程組找條件性質/變形 求解,即為原方程組的解。

    又等價於:

 

 

 

     

 

 

     方程通解可由上文引理印證,也知lcm是通解的最小變化量。其實上圖第一行變形的等號右邊應該是b2-b1,代碼中寫的也是b2-b1。exgcd中的參數可以直接傳a1和a2(不用傳-a2),這樣得到的k1,k2乘上(b2-b1)/exgcd 后,k1是正確值,k2是正確值的相反數,但要求出x,只需求出k1和k2中的一個正確值就好。

    故對於 n 個方程,我們順次合並兩個方程即可。在合並過程中發現一處無解,那么方程組就無解。

 

  時間復雜度:nlogn

  啟發思路:

    對於有很多個方程的方程組,可以划分部分,在各個部分里求出滿足當前部分的所有方程的解,最后再求同時滿足各部分的解的解,即為總方程組的解(分治再合並);

    以后在整數意義下有兩個未知數的方程都可解了。

  雜想:擴展CRT會取代中國剩余定理嗎?在OI中,擴展CRT更泛用,運算時的數值范圍大頭在求lcm上,比普通CRT不需要實現判斷模數兩兩互質、更不易溢出數據類型。但要明白,能逐個合並方程的前提是我們用計算機寫代碼,讓計算機做這些重復的活,而中國剩余定理直接給出一個最后答案的表達式,適合人工計算,這也是日常中現階段數學和信息的一個區別吧。

  例題:P4777 【模板】擴展中國剩余定理(EXCRT)https://www.luogu.com.cn/problem/P4777

  AC代碼:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 
 6 #define ll long long
 7 #define ull unsigned long long
 8 #define min(a,b) ((a)>(b)?(b):(a))
 9 #define max(a,b) ((a)>(b)>(a):(b))
10 #define swap(a,b) ((a)^=(b),(b)^=(a),(a)^=(b))
11 
12 using namespace std;
13 
14 const int N=1e5+5;
15 
16 ll n,ai[N],bi[N],a1,b1,m,k1,k2; 
17 
18 inline ll read()//int N+
19 {
20     ll x=0;
21     char ch=getchar();
22     while(!isdigit(ch)) ch=getchar();
23     while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
24     return x;
25 }
26 
27 ll exgcd(ll a,ll b,ll &x,ll &y)
28 {
29     if(!b)
30     {
31         x=1;y=0;//此題a,b都非負 
32         return a;
33     }
34     ll d=exgcd(b,a%b,y,x);
35     y-=a/b*x;
36     return d;
37 }
38 
39 inline ll mul(ll a,ll b,ll mod)//快速乘,防溢出
40 {
41     ll ret=0;
42     bool f=0;
43     if(b<0)
44     {
45         f=1;
46         b=-b;
47     }
48     while(b)
49     {
50         if(b&1)
51             ret=(ret+a)%mod;
52         b>>=1;
53         a=(a+a)%mod;
54     }
55     return f?-ret:ret;
56 }
57 
58 int main()
59 {
60     n=read();
61     for(int i=1;i<=n;++i)
62         ai[i]=read(),bi[i]=read();
63     m=a1=ai[1],b1=bi[1];
64     ll c,d;
65     for(int i=2;i<=n;++i)//EXCRT算法核心過程 
66     {
67         c=bi[i]-b1;
68         d=exgcd(a1,ai[i],k1,k2);
69         m*=ai[i]/d;
70         b1=(mul(mul(k1,c/d,m),a1,m)+b1)%m;//(k1*(c/d)*a1+b1)%m
71         a1=m;
72     }
73     if(b1<0)
74         b1+=m;//答案要>=0 
75     printf("%lld",b1);
76     return 0;
77 }

    思路很簡單,對溢出的處理廢了點思考時間。說實話該代碼沒有對exgcd中做防溢出處理,luogu的數據還是弱了。除了高精度(極限情況下數值范圍是個2*18*log1018 =2176位數,)有正確性更優的算法:https://www.luogu.com.cn/blog/user31435/solution-p4777

    思考過程想到的性質:

      lcm(a,b) >=lcm(b,a%b) 當a<b時等號成立

      若想防止溢出,exgcd中不能簡單地對x,y模b/d,a/d。通解公式中x,y一個變化了,另一個也會變化。

雜項:

  兩算法都不要忘判斷無解的情況。擴展歐幾里得初步求出的解的是ax+by=gcd(a,b)的解,不代表ax+by=c一定有解。

  做數據范圍很大的乘法時可能要注意模不同數時不同的應用環境,例如在CRT解的公式的最終結果是對M取模,除了前文提到的,計算時不能隨便對某個模數處處取模。很多式子后面沒有注明“模的意義下”,不要一看到大數計算就想當然要取模,會礙到/誤導 思考分析。

  為什么在取模意義下的乘法式子中無論以任意順序取模,結果都是正確且唯一的:設模數為m,一個數/式子的結果都能分解成km+r的形式,由分配律拆開后,km的那部分無論乘什么,結果在模意義下都是0,那么原式的結果就等於給那個數/式子取模后的新式子的結果。

  同余與等式常常轉換。

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM