一、題意:
給出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
所以d為b,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)*b(div為整除)
所以有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的可行解,但是題目中並沒有說c與a,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 (k∈Z)
至此,我們得到了通解的方程
x=x0+k*b
y=y0-k*a (k∈Z)
這樣,所有滿足ax+by=c的可行解都可求出。
三、具體實現
有了主體算法,下面要談到具體實現了。
先處理一下無解的情況:
1、當a=0並且b=0,而c≠0時,顯然無解;
當a=0,b=0,而c=0時,[x1,x2],[y1,y2]都為可行解,根據乘法原理,可行解的個數為(x2-x1+1)*(y2-y1+1);
2、當a=0 b≠0時:
此時即為求解by=c,則y=c/b,
如果c/b不是整數或c/b不在[y1,y2]的范圍內,無解
否則[x1,x2]內全部整數都為可行解.
3、當b=0,a≠0時,同上。
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時,我們需要的可行的k為3,4,5,所以需要將2.5向上取整得到3,5.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++,有些地方寫的比較傻,請諒解並希望給予指正。