數學/擴展歐幾里得/sgu 106 The equation


 

一、題意:

    給出a,b,c,x1,x2,y1,y2,求滿足ax+by+c=0,且x[x1,x2],y[y1,y2]的整數解個數。

二、分析:

    對於解二元一次不定方程,容易想到利用擴展歐幾里得求出一組可行解后找到通解,下面來介紹一下歐幾里得以及擴展歐幾里得。

1、歐幾里得:

又名輾轉相除法,是用來計算兩個數的最大公約數,其中就是利用gcd(a,b)=gcd(b,a mod b)來求解。下證gcd(a,b)=gcd(b,a mod b)的正確性:

 

a,b的一個公約數為d

a mod b=r,則a=kb+r(k為整數)r=a-kb

因為d|a,d|b

所以d|a-kb,d|r,而r=a mod b

所以db,a mod b的公約數

又因為d也為a,b的公約數,所以(a,b)(b,a mod b)的公約數一樣,所以最大公約數必然一樣,得證。

 

代碼描述:

1 int gcd(int a,int b)
2 {
3     if (b==0) return a;
4     return gcd(b,a%b);
5 }

 

2、擴展歐幾里得

    顧名思義,為上述歐幾里得算法的擴展。歐幾里得是用來求a,b的最大公約數,那么擴展歐幾里得不僅能求出a,b的最大公約數,還能求出滿足ax+by=gcd(a,b)的一組可行解。

求解過程中,擴展歐幾里得比歐幾里得多了一個賦值過程,具體證明如下:

 

ax1+by1=gcd(a,b),bx2+(a mod b)y2=gcd(b,a mod b)

因為由歐幾里得算法可知,gcd(a,b)=gcd(b,a mod b)

所以ax1+by1=bx2+(a mod b)y2

因為a mod b=a-(a div b)*bdiv為整除)

所以有ax1+by1=bx2+(a-(a div b)*b)y2

將右邊移項,展開得:

ax1+by1=ay2+bx2-(a div b)*b*y2

             =ay2+b[x2-(a div b)]y2

所以可得:x1=y2

              y1=x2-(a div b)*y2

將得到的的x1,y1遞歸操作求解x2,y2,如此循環往復,將會像歐幾里得一樣得到b=0的情況,此時遞歸結束,返回x=1,y=0,回溯得解。

 

代碼描述:

此函數返回的是a,b的最大公約數,同時也求解出滿足ax+by=gcd(a,b)的一組可行的(x,y)

 

1 int exgcd(int a,int b,int &x,int &y)
2 {
3     if (b==0) {x=1;y=0;return a;}
4     int t=exgcd(b,a%b,x,y);
5     int x0=x,y0=y;
6     x=y0;y=x0-(a/b)*y0;
7     return t;
8 }

 

 

 

 

3、關於求解二元一次不定方程ax+by=c

    首先,如果c不是gcd(a,b)的倍數,方程顯然無解。

擴展歐幾里得求解的是ax+by=gcd(a,b)=1的可行解,但是題目中並沒有說ca,b互質之類的條件,所以需要在開始時兩邊同時除以gcd(a,b)

d=gcd(a,b)

a'=a/d,b'=b/d,c'=c/d,

則下面需要求解a'x+b'y=c'的整數解,而gcd(a',b')=1

則我們只需求a'x+b'y=1的可行解

直接使用擴展歐幾里得,得到(x',y'),則最終解為x'*c',y'*c'設為(x0,y0)

 

現在得到了一組可行解,但是如何得到通解呢?

(x0,y0)代入ax+by=c,則有

a*(x0)+b*(y0)=c

通過拆添項,可有:

a*(x0+1*b)+b*(y0-1*a)=c

a*(x0+2*b)+b*(y0-2*a)=c

a*(x0+3*b)+b*(y0-3*a)=c

……

a*(x0+k*b)+b*(y0-k*a)=c  (kZ)

至此,我們得到了通解的方程

x=x0+k*b

y=y0-k*a  (kZ)

這樣,所有滿足ax+by=c的可行解都可求出。

 

三、具體實現

有了主體算法,下面要談到具體實現了。

先處理一下無解的情況:

1、當a=0並且b=0,而c0時,顯然無解;

   當a=0,b=0,而c=0時,[x1,x2],[y1,y2]都為可行解,根據乘法原理,可行解的個數為(x2-x1+1)*(y2-y1+1);

2、當a=0 b0時:

此時即為求解by=c,則y=c/b

如果c/b不是整數或c/b不在[y1,y2]的范圍內,無解

否則[x1,x2]內全部整數都為可行解.

3、當b=0,a0時,同上。

4、若c不是gcd(a,b)的個數,方程顯然無解。

 

處理完了一些繁瑣的細節后,下面是具體的求解過程:

1、擴展歐幾里得求解的是ax+by=c,而本題是ax+by+c=0,需將c移項。

2、對於本道題,首先要注意的是,對於負數的模運算在此算法中無法得到正確解,所以要處理一下a,b,c的正負情況。

如果a為負數,只需將a取相反數后,再處理一下x[x1,x2]的范圍。當a取了相反數,相當於把x也取反,則需要把x的范圍由[x1,x2]轉變成[-x2,-x1],類似於把數軸反了過來。

b同理。

3、利用擴展歐幾里得解二元一次不定方程,得到一組可行解(x0,y0)

4、因為題目中對x,y有條件約束,而有x=x0+kb,y=y0-kb,我們可以求出滿足x[x1,x2],y[y1,y2]k的取值范圍

即為求解x1<=x0+kb<=x2,y1<=y0-kb<=y2的整數k的個數

但是在求解這兩個一次函數的過程中,會有除不盡的現象,該如何取整呢?

舉個例子

當出現2.5<=k<=5.5時,我們需要的可行的k3,4,5,所以需要將2.5向上取整得到35.5向下取整得到5,即為3<=k<=5

當出現-5.5<=<=-2.5時,我們需要的可行的k-5,-4,-3,所以需要將-5.5向上取整得到-5,-2.5向下取整得到-3,即為-5<=k<=-3

正負數的情況都已經考慮完全了,可以得到取整的結論:上界下取整,下界上取整。

最后,將得到的兩個范圍取交集,得到[l,r],則最終答案為r-l+1

 

這樣,本題就可以完美解決了。

 

 1 // BY Rinyo
 2 
 3 #include<cstdio>
 4 #include<cmath>
 5 long long a,b,c,x1,x2,yy1,y2,x0,yy0;
 6 inline long long cmin(const long long &x,const long long &y) {return x<y?x:y;}
 7 inline long long cmax(const long long &x,const long long &y) {return x>y?x:y;}
 8 
 9 long long gcd(long long a,long long b)
10 {
11     if (b==0) return a;
12     return gcd(b,a % b);
13 }
14 void exgcd(long long a,long long b)
15 {
16     if (b==0){x0=1;yy0=0;return;}
17     exgcd(b,a%b);
18     long long t=x0;x0=yy0;yy0=t-a/b*yy0;
19     return;
20 }
21 
22 int main()
23 {
24     scanf("%I64d%I64d%I64d%I64d%I64d%I64d%I64d",&a,&b,&c,&x1,&x2,&yy1,&y2);
25     c=-c;
26     if (c<0) {a=-a;b=-b;c=-c;}
27     if (a<0) {a=-a;long long t=x1;x1=-x2;x2=-t;}
28     if (b<0) {b=-b;long long t=yy1;yy1=-y2;y2=-t;}
29     if (a==0 && b==0)
30     {
31         if (c==0)
32         {
33             printf("%I64d",(x2-x1+1)*(y2-yy1+1));
34             return 0;
35         }
36         printf("0");return 0;
37     }
38     else if (a==0)
39     {
40         if (c %b ==0)
41             if (c/b<=y2 && c/b>=yy1) {printf("%I64d",x2-x1+1);return 0;}
42         printf("0");return 0;
43     }
44     else if (b==0)
45     {
46         if (c%a==0)
47             if (c/a<=x2 && c/a>=x1) {printf("%I64d",y2-yy1+1);return 0;}
48         printf("0");return 0;
49     }
50 
51     long long d=gcd(a,b);
52     if (c%d!=0){printf("0");return 0;}
53 
54     a=a/d;b=b/d;c=c/d;
55     exgcd(a,b);
56     x0=x0*c;yy0=yy0*c;
57 
58     double tx2=x2,tx1=x1,tx0=x0,ta=a,tb=b,tc=c,ty1=yy1,ty2=y2,ty0=yy0;
59     long long down1=floor(((tx2-tx0)/tb)),down2=floor(((ty0-ty1)/ta));
60     long long r=cmin(down1,down2);
61     long long up1=ceil(((tx1-tx0)/tb)),up2=ceil(((ty0-ty2)/ta));
62     long long l=cmax(up1,up2);
63     if (r<l) printf("0");
64     else printf("%I64d",r-l+1);
65     return 0;
66 
67 }

由於剛從pascal轉c++,有些地方寫的比較傻,請諒解並希望給予指正。


免責聲明!

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



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