题意
坐标轴上,一个人想从
A
A
点走到 点,每次移动可以向左或向右走
a
a
个单位、 个单位或
a+b
a
+
b
个单位,求最少移动多少次。
−231≤A,B<231
−
2
31
≤
A
,
B
<
2
31
0<a,b<231
0
<
a
,
b
<
2
31
思路
先将原题转化为向右移动
B−A
B
−
A
个单位需要的最小步数。如果不考虑长度为
a+b
a
+
b
的移动,不难看出,不可能向左移动
a
a
长度后,又向右移动 长度。那么可以设向右的
a
a
移动作了 次(如果向左移动
k1
k
1
为负),向右的
b
b
移动作了 次。所以这个移动的次数即为
|k1|+|k2|
|
k
1
|
+
|
k
2
|
。用扩展欧几里得求出
k1,k2
k
1
,
k
2
的特解后,写出通解
k1=k′1+bK
k
1
=
k
1
′
+
b
K
,
k2=k′2−aK
k
2
=
k
2
′
−
a
K
。
当
k1
k
1
和
k2
k
2
同号时,结果为
max(k1,k2)
m
a
x
(
k
1
,
k
2
)
,当
k1
k
1
和
k2
k
2
异号时,结果为
|k1|+|k2|
|
k
1
|
+
|
k
2
|
,我们可以考虑使
|k1−k2|
|
k
1
−
k
2
|
最小。
而
|k1−k2|=|k′1+bK+k′2−aK|=|k′1−k′2+(b−a)K|
|
k
1
−
k
2
|
=
|
k
1
′
+
b
K
+
k
2
′
−
a
K
|
=
|
k
1
′
−
k
2
′
+
(
b
−
a
)
K
|
故当原式为零时
K′=k2′−k′1a+b
K
′
=
k
2
′
−
k
′
1
a
+
b
但原式不一定正好为零,所以在
[K′−1,K′+1]
[
K
′
−
1
,
K
′
+
1
]
这个范围搜索,找到最优解即可。
代码
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define Abs(x) ((x>0)?(x):-(x))
typedef long long LL;
using namespace std;
LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
void exgcd(LL a,LL b,LL &x,LL &y)
{
if(!b){x=1,y=0;return;}
exgcd(b,a%b,y,x);y-=a/b*x;
}
bool Exgcd(LL &A,LL &k1,LL &B,LL &k2,LL C)
{
LL g=gcd(A,B);
if(C%g)return 0;
A/=g,B/=g,C/=g;
exgcd(A,B,k1,k2);
k1*=C,k2*=C;
return 1;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
LL A,B,a,b,k1,k2;
scanf("%lld%lld%lld%lld",&A,&B,&a,&b);
if(!Exgcd(a,k1,b,k2,B-A))
{
printf("-1\n");
continue;
}
int t=(k2-k1)/(a+b);
LL ans=1e15;
FOR(i,t-1,t+1)
{
LL k_1=k1+b*i,k_2=k2-a*i;
if(Abs(k_1+k_2)==Abs(k_1)+Abs(k_2))
ans=min(ans,max(Abs(k_1),Abs(k_2)));
else ans=min(ans,Abs(k_1)+Abs(k_2));
}
printf("%lld\n",ans);
}
return 0;
}