はまやんはまやんはまやん

hamayanhamayan's blog

Chef and Universe [CodeChef December Challenge 2017 F]

https://www.codechef.com/DEC17/problems/CHEFUNI

座標(0,0,0)にいる
ここから以下の操作をして最小コストで座標(X,Y,Z)に移動せよ。

  • コストAで、x,y,z座標のどれか1つをインクリメントする
  • コストBで、x,y,z座標のどれか2つをインクリメントする
  • コストCで、x,y,z座標の全てのインクリメントする

解法

基本は貪欲に解く。
「コスト/1座標進める」でコスパが良いものを選択していく。
 
この貪欲の過程で「今残っている進めるべき座標からBの操作を最大何回行えるか」の関数が必要。
getmaxb(x, y, z) := 座標を(+x,+y,+z)するときにBの操作を最大何回行えるか
x,y,zを小さい順にv[0],v[1],v[2]とする。
v[0]+v[1]≦v[2]となれば、v[0]とv[2],v[1]とv[2]で操作をしていけば最高回数が達成できる。
そうではない場合は、v[0]+v[1]≦v[2]となるまで、Bを三回行い、全て-2していく操作を行う。
これを愚直にやっていると遅いので、何回-2していけばいいかを考えてみよう。
Bを3回行う操作を1回行うと、不等式の左辺は-4, 右辺は-2される。
よって差が2だけ縮まる。
そのため、delta=v[0]+v[1]-v[2]の差を埋めるためにceil(delta/2)回操作を行えば良いと分かる。
後、この操作はfloor(v[0]/2)が限界なので、どちらか小さい方で削る。
削ってv[0]+v[1]≦v[2]となれば最高回数を達成させる。
これだけ操作をしてみて、まだちょっと残っていれば、大きい方からペアで貪欲に消していって消せなくなったら答え。

(i) Aが一番コスパが良い場合(A≦B/2 かつ A≦C/3)
全てAで処理してしまうのが良い

(ii) Bが一番コスパが良い場合(B/2<A かつ B/2≦C/3)
Cは1回か0回やれば十分であることが分かる。
(Bを3回やればCを2回やったのと同等のことができるため)
なので、Cは0回か1回かどちらも試してみる。
後は、なるべくBで進めたあとに残りをAで進める。

(iii) Cが一番コスパが良く、BよりもAの方がコスパがいい場合(A≦B/2)
なるべくCで進め、残ったやつをAで進める。

(iv) Cが一番コスパが良く、次にBが良く、Aが最も悪い
cを使う回数で三分探索して最小値をあぶり出すが、普通にやると凹関数になってない。
実験してみると、cが偶数のとき、cが奇数のときで分けて見ると凹関数になっている。
(なぜかは分からない)
なので、別々に三分探索してやって、コストが小さい方が答え

template<typename Func>
int findMin(int L, int R, Func f) { //[L, R)
    int lo = L - 1, hi = R - 1;
    while (lo + 1 != hi) {
        int mi = (lo + hi) / 2;
        if (f(mi) <= f(mi + 1)) hi = mi;
        else lo = mi;
    }
    return hi;
}



typedef long long ll;
#define INF 1LL<<60
ll X, Y, Z, A, B, C;
//---------------------------------------------------------------------------------------------------
ll getmaxb(ll x, ll y, ll z) {
    vector<ll> v;
    v.push_back(x); v.push_back(y); v.push_back(z);
    sort(v.begin(), v.end());

    if (v[0] + v[1] <= v[2]) return v[0] + v[1];

    ll dmax = v[0] / 2;
    ll delta = v[0] + v[1] - v[2];
    ll dwant = (delta + 1) / 2;
    ll d = min(dmax, dwant);

    ll res = d * 3;
    v[0] -= d * 2;
    v[1] -= d * 2;
    v[2] -= d * 2;

    if (v[0] + v[1] <= v[2]) {
        res += v[0] + v[1];
        v[2] -= (v[0] + v[1]);
        v[0] = 0;
        v[1] = 0;
    }

    sort(v.begin(), v.end());
    while (0 < v[1]) {
        res++;
        v[1]--;
        v[2]--;
        sort(v.begin(), v.end());
    }

    return res;
}
//---------------------------------------------------------------------------------------------------
ll sm;
ll d = 0;
ll f(ll c) {
    if (c < 0) return INF;
    c = c * 2 + d;
    if (X - c < 0 or Y - c < 0 or Z - c < 0) return INF;

    ll x = X - c;
    ll y = Y - c;
    ll z = Z - c;

    ll b = getmaxb(x, y, z);

    return c * C + b * B + (sm - c * 3 - b * 2) * A;
}
//---------------------------------------------------------------------------------------------------
ll solve() {
    sm = X + Y + Z;
    ll mi = min(X, min(Y, Z));
    
    // A is good
    if (A * 2 <= B and A * 3 <= C) return sm * A;

    // B is good
    if (B < A * 2 and B * 3 <= C * 2) {
        ll ans = INF;

        // don't use rule C
        ll b = getmaxb(X, Y, Z);
        ans = b * B + (sm - b * 2) * A;

        // use rule C once
        if (0 < mi) {
            ll b = getmaxb(X - 1, Y - 1, Z - 1);
            ans = min(ans, C + b * B + (sm - b * 2 - 3) * A);
        }

        return ans;
    }

    // C is good

    // A <= B
    if (A * 2 <= B) return mi * C + (sm - mi * 3) * A;

    // B < A
    ll ans = INF;
    d = 0;
    ll gc = findMin(0, mi / 2 + 1, f);
    ans = min(ans, f(gc));
    d = 1;
    gc = findMin(0, mi / 2 + 1, f);
    ans = min(ans, f(gc));
    return ans;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    int T; cin >> T;
    rep(t, 0, T) {
        cin >> X >> Y >> Z >> A >> B >> C;
        ll a = solve();
        printf("%lld\n", a);
    }
}