1. 程式人生 > >解不定方程(從HDU1356說起)

解不定方程(從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;
	}
}