题目链接:http://poj.org/problem?id=2142
题目大意:现有一个天平和质量分别为a和b的砝码,已知砝码数量不限且天平左右均可放置砝码,要求在天平上称出质量为d的物品。找出一种可行的方案满足:放置的砝码数尽量少;在砝码数相同的情况下,砝码的总质量尽量小。
分析:我们设a,b砝码的数量分别为x和y,那么问题就转化为了求不定方程ax+by=d的一组整数解(x,y)满足|x|+|y|尽量小,在|x|+|y|相等的情况下a|x|+b|y|尽量小(x<0表示砝码和物品在同一侧)。
一种方法是:我们用扩展欧几里得可以求出方程的一个特解(x0,y0),进而可以得出方程的通解:x=x0+(b/d)*t;y=y0-(a/d)*t(t为正整数);我们不妨设a>b,那么|x|+|y|最小时,即|x0+(b/d)*t|+|y0-(a/d)*t|最小,另后一项为0,则得到t=y0*d/a,容易知道最终的解在t周围,我们可以枚举[ t-10,t+10 ]内的x,y值,从中找到最小的一组。
实现代码如下:
#include <iostream> #include <cstdio> using namespace std; typedef long long LL; LL a,b,c,d,x0,y0; void exgcd(LL a,LL b,LL &d,LL &x,LL &y) { if(!b) { x=1,y=0,d=a; return ; } else { exgcd(b,a%b,d,x,y); LL t=x; x=y; y=t-(a/b)*y; } } LL abs(LL x) { return x>0?x:-x; } int main() { while(scanf("%lld%lld%lld",&a,&b,&c)) { if(a==0&&b==0&&c==0) break; bool flag=false; if(a<b) { flag=true; swap(a,b); } exgcd(a,b,d,x0,y0); x0=x0*(c/d); y0=y0*(c/d); LL t=y0*d/a,x,y; LL ans=999999999; for(LL i=t-10;i<=t+10;i++) { LL x1=x0+(b/d)*i; LL y1=y0-(a/d)*i; if(abs(x1)+abs(y1)<ans) { ans=abs(x1)+abs(y1); x=x1; y=y1; } } if(flag) printf("%lld %lld\n",abs(y),abs(x)); else printf("%lld %lld\n",abs(x),abs(y)); } return 0; }
我们也可以这样考虑:要使|x|+|y|最小,无非是x取最小值或y取最小值,我们只需把这两个最小值分别找出来比较即可。
实现代码如下:
#include <iostream> #include <cstdio> using namespace std; typedef long long LL; LL a,b,c,d,x0,y0; void exgcd(LL a,LL b,LL &d,LL &x,LL &y) { if(!b) { x=1,y=0,d=a; return ; } else { exgcd(b,a%b,d,x,y); LL t=x; x=y; y=t-(a/b)*y; } } LL abs(LL x) { return x>0?x:-x; } int main() { while(scanf("%lld%lld%lld",&a,&b,&c)) { if(a==0&&b==0&&c==0) break; exgcd(a,b,d,x0,y0); a/=d;b/=d;c/=d; LL x1=x0*c; x1=(x1%b+b)%b; LL y1=(c-a*x1)/b; //printf("x1=%lld y1=%lld\n",x1,y1); LL y2=y0*c; y2=(y2%a+a)%a; LL x2=(c-b*y2)/a; //printf("x2=%lld y2=%lld\n",x2,y2); if(x1+abs(y1)>abs(x2)+y2) printf("%lld %lld\n",abs(x2),y2); else printf("%lld %lld\n",x1,abs(y1)); } return 0; }