競プロやります。

競プロを頑張るブログです。

AtCoder Beginner Contest 119

C - Synthetic Kadomatsu

https://atcoder.jp/contests/abc119/tasks/abc119_c
コンテスト中に解けませんでした。。。悲しい。

方針

まず  N 本の全ての竹について、「Aで使う、Bで使う、Cで使う、使わない」の4通りに分類します。この分類のときに A, B, C を得るのに必要な魔力を求めることは簡単で、まず使うものについては最初に A 用, B 用, C 用それぞれで合成してしまい、あとは A, B, C との差分を延長か短縮すればよいです。
分類の方法は  4^N 通りですが、 N \leq 8 なので全ての分類方法について必要な魔力を求められます。それらのうちの最小値が答えとなります。
A, B, C それぞれについて、少なくとも 1 本は元となる竹を選ぶ必要があることに注意します。

竹の分類の実装についてですが、4 進数表記で N 桁になる数(ただし、上位桁に 0 が来ても良いものとする)を用います。このような数は 0 から  4^N-1 です。各桁について 1 なら A、2 なら B、3 なら C で使い、0 なら使わないと考えればよいです。

下図のようなイメージです(  N = 5 )。
f:id:poporix:20190224234056p:plain:w300

ソースコード

#include <bits/stdc++.h>
using namespace std;

int main(){
    int N, A, B, C;
    cin >> N >> A >> B >> C;
    vector<int> l(N);
    for(int i=0; i<N; i++) cin >> l[i];

    int ans = 1e9;
    for(int i=0; i<pow(4,N); i++){
        int n = i;
        int a = 0, b = 0, c = 0; // a,b,cをそれぞれA,B,Cにする
        int tmp = 0;  // この分類方法のときに必要な魔力
        int d = 0; // いま n の 4 進数表記で下から何桁目を見ているかを表す
        while(n>0){
            // n%4 が 1, 2, 3のとき、それぞれ a, b, c に使う。
            if(n%4==1){
                if(a!=0) tmp+=10; // a!=0 に l[d] を合成するには魔力10が必要。以下同様。
                a += l[d];
            }else if(n%4==2){
                if(b!=0) tmp+=10;
                b += l[d];
            }else if(n%4==3){
                if(c!=0) tmp+=10;
                c += l[d];
            }
            n/=4;
            d++;
        }
        // 最低 1 本は元になる竹を選ぶ(無から竹を延長していくのはNG)
        if(a==0 || b==0 || c==0) continue;

        // 不足分を延長or短縮する
        tmp += abs(a-A) + abs(b-B) + abs(c-C);
        ans = min(ans, tmp);
    }
    cout << ans << endl;
}

D - Lazy Faith

https://atcoder.jp/contests/abc119/tasks/abc119_d

方針

位置  x 以東にある、位置  x に最も近い神社・寺の位置をそれぞれ  s_r,~t_r とします。
同様に、位置  x 以西にある、 x に最も近い神社・寺の位置をそれぞれ  s_l,~t_l とします。
これら  s_r,~t_r などは二分探索によって求めることが出来ます。

すると、移動の方法は図の (1)~(4) のどれかになるので、これらを全通り試して最も移動距離の小さいものを出力すればよいです。ただし、図では  s_l < t_l および  s_r < t_r としていますが、他の場合もあります。
f:id:poporix:20190225004051p:plain:w300

(1) のとき
 s_r,~t_r に訪問します。どちらにも到達しなければならないので、この 2 つのうち遠い方まで行く必要があります。
移動距離は  d = \max(s_r - x, t_r - x) です。

(2) のとき
(1)と全く同じように考えて、  d = \max(x-s_l, x-t_l) です。

(3) のとき
 x \to t_l \to x \to s_r と訪問する場合と、 x \to s_l \to x \to t_r と訪問する場合とがあります。
よって、 d = \min(2(x-s_l) + (t_r-x),~2(x-t_l)+(s_r-x)) です。

(4) のとき
(3)と全く同じように考えて、 d = \min(2(s_r-x) + (x-t_l),~2(t_r-x)+(x-s_l)) です。

以上で求めた  d の中で最も小さいものを出力します。

ところで、以下の2つの場合に注意する必要があります。神社について書きますが、もちろん寺でも同様です。
 x が全ての神社より東のとき
このとき  s_r は存在しないことになりますが、 s_r = \text{inf} としておけば上記の議論をそのまま適用できます。

 x が全ての神社より西のとき
このとき  s_l は存在しませんが、同様に  s_l = -\text{inf} とすればよいです。

以上二つは、あらかじめ  s_i の中に inf と -inf を入れておくことで実現できます。 s_i は昇順に並んでいることに注意します。

ソースコード

#include <bits/stdc++.h>
#define inf 1e15
using namespace std;
typedef long long ll;

int main(){
    int A, B, Q;
    cin >> A >> B >> Q;
    vector<ll> s(A+2), t(B+2);
    for(int i=1; i<=A; i++) cin >> s[i];
    for(int i=1; i<=B; i++) cin >> t[i];
    // -inf と inf を追加しておく
    s[0] = -inf;  s[A+1] = inf;
    t[0] = -inf;  t[B+1] = inf;

    while(Q>0){
        ll x; cin >> x;
        ll sr, sl, tr, tl;
        // x 以東で x に最も近い s, t の index を is, it とする。 
        int is = lower_bound(s.begin(), s.end(), x) - s.begin();
        int it = lower_bound(t.begin(), t.end(), x) - t.begin();
        sr = s[is];  sl = s[is-1];
        tr = t[it];  tl = t[it-1];

        ll ans = 1e18;
        ans = max(sr-x, tr-x);  // (1)
        ans = min(ans, max(x-sl, x-tl)); // (2)
        ans = min(ans, 2*(x-sl) + tr-x); // (3)
        ans = min(ans, 2*(x-tl) + sr-x); // (3)
        ans = min(ans, 2*(sr-x) + x-tl); // (4)
        ans = min(ans, 2*(tr-x) + x-sl); // (4)
        cout << ans << endl;

        Q--;
    }
}