1. 程式人生 > >ZOJ ~ 3593 ~ One Person Game (擴充套件歐幾里得,不定方程)

ZOJ ~ 3593 ~ One Person Game (擴充套件歐幾里得,不定方程)

題意

你要從A走到B,你每次可以走a步,b步,a+b步問最小需要走多少步?無法到達輸出 -1。

題解

先不考慮a+b步的情況,那麼我們要求解的就是:ax+by=|A-B|,如果|A-B|\%gcd(a,b) != 0,證明無解。

假設原方程一組解為x0,y0,那麼通解(x,y)為:& (x0+b'*t,\quad y0-a'*t)a'=a/gcd,\quad b'=b/gcd
其實也就是兩條直線:x = b't+x0y = -a't+y0

取一條平行於y軸的直線 x = t :

如果 x 和 y 異號,假設x > 0,y < 0也就是往前走x次a步,往後走y次b步。x < 0, y > 0同理,這種情況答案為|x| + |y|

如果 x 和 y 同號,其實也就是都往前(後)走x次a步,y次b步,考慮上可以走a+b的情況,答案也就是max(|x| , |y|)

結合圖可知,越接近交點值越小,當 t 取 x 和 y 交點時,答案最小。

a't+x0=-b't+y0 =》t = (y0-x0)/(a'+b') ,但是 t 可能不是整數,所以我們要嘗試 t-1,t,t+1 三個值。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
void exgcd(LL a, LL b, LL& d, LL& x, LL& y)
{
    if (!b) { d=a; x=1; y=0; }
    else { exgcd(b, a%b, d, y, x); y -= x*(a/b); }
}
LL cal(LL x, LL y)
{
    if (x*y >= 0) return max(abs(x), abs(y));
    else return abs(x)+abs(y);
}
LL solve(LL a, LL b, LL c)
{
    LL GCD, x0, y0;
    exgcd(a, b, GCD, x0, y0);
    if (c%GCD) return -1;
    x0 *= c/GCD; y0 *= c/GCD;

    LL aa = a/GCD, bb = b/GCD;
    LL t = (y0-x0)/(aa+bb);//x0+a't=y0-b't,t = (y0-x0)/(a'+b')
    LL ans1 = cal(x0+bb*t, y0-aa*t);
    LL ans2 = cal(x0+bb*(t-1), y0-aa*(t-1));
    LL ans3 = cal(x0+bb*(t+1), y0-aa*(t+1));
    return min(min(ans1, ans2), ans3);
}
int main()
{
    int T; scanf("%d", &T);
    while (T--)
    {
        LL A, B, a, b; scanf("%lld%lld%lld%lld", &A, &B, &a, &b);
        LL ans = solve(a, b, abs(A-B));
        printf("%lld\n", ans);
    }
    return 0;
}
/*
2
0 1 1 2
0 1 2 4
*/