题目:
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4677
分析:
1、每步可以向左或向右走a/b/a+b步,我们先简化一下,只能走a/b步,则有方程a*x+b*y=abs(A-B)
2、求得x,y之后,我们思考怎么使步数最少:若能走a+b步,则其实也是由走a/b步组成的,那么首先要使|x|+|y|最小
3、怎么使|x|+|y|最小?我们已求得一组解(x0,y0),令tx=b/gcd,ty=a/gcd,通解为x=x0+k*tx,y=y0-k*ty,根据基本不等式,当且仅当|x|=|y|时取到min,解得k=(y0-x0)/(tx+ty)或k=(y0+x0)/(ty-tx),但k有可能是分数,则左右验证
4、若x*y>=0即x y同号,应取max(abs(x),abs(y))
若x*y<0即x y异号,应取abs(x)+abs(y)
感想:
求|x|+|y|最值时,有的人用这种方法:令|x|=0得k=-x0/tx,令|y|=0得k=y0/ty,当以上同时满足时取最小,由分数性质得,k=(y0-x0)/(tx+ty),然后只用验证第一种k。这个想法也挺神奇,本质还是基本不等式。
代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long int ll;
const ll inf=1e18;
ll exgcd(ll a,ll b,ll& x,ll& y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
ll r=exgcd(b,a%b,y,x);
y-=x*(a/b);
return r;
}
int main()
{
ll A,B,c,a,b,x,y,gcd,tx,ty,ans,tmpx,tmpy;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%lld%lld%lld%lld",&A,&B,&a,&b);
ans=inf;
if(A<B) swap(A,B);
c=A-B;
gcd=exgcd(a,b,x,y);
if(c%gcd!=0)
printf("-1\n");
else
{
x*=c/gcd;y*=c/gcd;
tx=b/gcd;ty=a/gcd;
ll mid=(y-x)/(tx+ty);
for(ll i=mid-1;i<=mid+1;i++)
{
tmpx=x+i*tx;
tmpy=y-i*ty;
if(tmpx*tmpy>=0) ans=min(ans,max(abs(tmpx),abs(tmpy)));
else ans=min(ans,abs(tmpx)+abs(tmpy));
}
mid=(y+x)/(ty-tx);
for(ll i=mid-1;i<=mid+1;i++)
{
tmpx=x+i*tx;
tmpy=y-i*ty;
if(tmpx*tmpy>=0) ans=min(ans,max(abs(tmpx),abs(tmpy)));
else ans=min(ans,abs(tmpx)+abs(tmpy));
}
printf("%lld\n",ans);
}
}
return 0;
}