解不定方程(從HDU1356說起)
傳送門:http://acm.hdu.edu.cn/showproblem.php?pid=1356
由題意,就是要解一個不定方程ax+by=d,要求(abs(x)+abs(y))最小。
一.exgcd
先從exgcd說起。由裴蜀定理可知,ax+by=gcd(a,b)必定存在整數解(x,y)。那麼有:
ax+by=gcd(a,b)=gcd(b,a%b)=bx'+(a%b)y' (1.1)
其中等號1,3用到了裴蜀定理,等號2用到了gcd的性質。
由a%b=a-a/b(本文中所有的除法都是整數除法)那麼我們把等式最右邊變形,可以得到:
ax+by=bx'+ay'-(a/b)y'=ay'+b(x'-(a/b)*y') (1.2)
於是得到一組解:
x=y' (1.3)
y=x'-(a/b)*y' (1.4)
所以可以用遞迴的方式求解。
最後考慮一下邊界條件,當b==0時,從線性同餘方程的角度看x=1,y=0顯然是一組可行解。
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1;
y=0;
return a;
}
int d=exgcd(a,b,x,y);
swap(x,y);//此時x=y',y=x'
y-=a/b*x;//考慮x,y的實際情況,也就是 y=x'-(a/b)*y'
return d;//順便吧gcd也求出來
}
二.求通解
再考慮這道題,等號右邊並不是gcd,而是一個d,因為題目告訴你一定有解,所以gcd一定是d的因子,我們把x和y都乘上一個rate=(d/gcd(a,b)),得到一組初始的
x0=x*rate (2.1)
y0=y*rate (2.2)
接下來就要去找對x0和y0加加減減,來求這個MIN(abs(x0+dx)+abs(y0-dy))。
我們先考慮
再考慮dx和dy的關係:
a*x0+b*y0=d (2.3)
對方程變形得到:
a*(x0+dx)+b*(y0-dy)=d (2.4)
把裡面的式子提出來得到:
a*dx-b*dy=0 (2.5)
那麼就是:
dx/dy=b/a (2.6)
考慮gcd(a,b),可以得到:
dx/dy=(b/gcd)/(a/gcd) (2.7)
此時(b/gcd)與(a/gcd)是互質的所以可以取到的最小的dx和dy就是:
dx=(b/gcd) (2.8)
dy=(a/gcd) (2.9)
那麼方程的通解一定滿足如下條件:
ansx=x0+k*dx (2.10)
ansy=y0-k*dy (2.11)
三.問題轉化
知道了通解,那麼哪一組解才是最優的呢?
因為dx!=dy,不妨設dx>dy,我們假設此時x滿足
|x|>>|dx|(>>表示遠大於)
那麼我們讓
x+=t*dx (3.1)
y-=t*dy (3.2)
在x和y均不變號的情況下,答案會變小t*(dx-dy),是一組更優的解。所以最優解一定滿足:
|x|<|dx| (3.3)
那麼問題就可以轉化為變成了求方程關於x的最小正整數解。
但是問題在於,根據式(3.3),去掉絕對值後,x可能是負的。但是問題在於,我們要求的方程
a*x+b*y=d (3.4)
可以變形為
y=(d-a*x)/b (3.5)
我們上面已經證明,通過上述方程構造出來的x,上面方程中的y一定是有解的,那麼對於一個確定的x,我們總希望y最小,因為dx>dy,那麼如果x取到負數,那麼一定會大大增加y的值,所以很有可能會更糟。所以我們只要考慮x取最小正整數的情況就可以了。(我直言了,我不會證明,但是我的直覺告訴我這樣做很對,oj也告訴我這樣做很對)。
反之,如果dy>dx,那麼考慮y的最小正整數解即可。
四.求最小正整數解
那這就非常容易了(說白了就是取模):
ansx=(ansx%dx+dx)%dx
然後用式(3.5):
ansy=(d-a*ansx)/b
就完事了。
五.貼程式碼
看清題意,多寫兩個if就完事了
#include<iostream>
using namespace std;
#define ABS(x) (((x)>0)?(x):-(x))
int x,y,x1,x2,y1,y2,dx,dy;
int exgcd(int a,int b,int &x,int &y){
if(!b){x=1,y=0;return a;}
int d=exgcd(b,a%b,x,y);
int t=x;x=y,y=t-(a/b)*x;
return d;
}
void read(int &x)
{
x=0;int f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
x*=f;return;
}
int main(){
int a,b,d;
while(1){
read(a),read(b),read(d);
if(!a&&!b&&!d)break;
int gcd=exgcd(a,b,x,y);
int rate=d/gcd;
dx=b/gcd,dy=a/gcd;
x1=x2=x*rate;
y1=y2=y*rate;
x1=(x1%dx+dx)%dx,y1=ABS(d-a*x1)/b;
y2=(y2%dy+dy)%dy,x2=ABS(d-b*y2)/a;
if(x1+y1<x2+y2||(x1+y1==x2+y2&&a*x1+b*y1<a*x2+b*y2))cout<<x1<<" "<<y1<<endl;
else cout<<x2<<" "<<y2<<endl;
}
}