ABC 398

https://atcoder.jp/contests/abc398

A. Doors in the Center

https://atcoder.jp/contests/abc398/tasks/abc398_a

B. Full House 3

https://atcoder.jp/contests/abc398/tasks/abc398_b

C. Uniqueness

https://atcoder.jp/contests/abc398/tasks/abc398_c

D. Bonfire

https://atcoder.jp/contests/abc398/tasks/abc398_d

煙の位置は変えずに焚き火と高橋くんの位置を移動させる。

void solve() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    ll n, row, col;
    cin >> n >> row >> col;
    string s;
    cin >> s;

    using P = pair<ll, ll>;
    set<P> memo;
    memo.insert({0, 0});

    int si = 0, sj = 0;

    for (char c : s) {
        if (c == 'N') {
            si++;
            row++;
        }
        if (c == 'W') {
            sj++;
            col++;
        }
        if (c == 'S') {
            si--;
            row--;
        }
        if (c == 'E') {
            sj--;
            col--;
        }
        memo.insert({si, sj});
        if (memo.count({row, col})) {
            cout << 1;
        } else {
            cout << 0;
        }
    }
    cout << endl;
}

E. Tree Game

https://atcoder.jp/contests/abc398/tasks/abc398_e

First の後に改行を入れ忘れてコンテスト中 AC できなかった。。。

ただ二部グラフであるということはわからずやっていたので AC できていたとしても実力でなくまぐれ AC だったのでまぁいいかという感じ。

奇閉路がない <=> 二部グラフである という条件があるらしい。言われてみれば確かにと思う。 色が違う頂点の組の数が奇数のときは先手必勝、偶数のときは後手必勝ということがわかる。

奇閉路がない <=> 二部グラフであると言えるのが何故か

ある頂点から同じ色の頂点への距離は2の倍数となる。よって同じ色どうしの頂点を結ぶと閉路の1周の距離は奇数となる。 逆に奇閉路のとき、必ず頂点のうちどこかで同じ色が隣り合わせになってしまう。 よって題意が言える。

void solve() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    using P = pair<int, int>;
    set<P> memo;
    int n;
    cin >> n;
    vvint graph(n);
    rep(i, n - 1) {
        int u, v;
        cin >> u >> v;
        u--, v--;
        graph[u].push_back(v);
        graph[v].push_back(u);
        memo.insert({u, v});
        memo.insert({v, u});
    }

    vint depth(n, 0);
    auto dfs = [&](auto dfs, int now, int p = -1) -> void {
        for (int nx : graph[now]) {
            if (nx == p)
                continue;
            depth[nx] = depth[now] + 1;
            dfs(dfs, nx, now);
        }
    };
    dfs(dfs, 0);

    set<P> es;
    int cnt = 0;
    rep(i, n) {
        rep2(j, i + 1, n) {
            if (memo.count({i, j}))
                continue;
            if ((depth[i] + depth[j]) % 2 == 1) {
                cnt++;
                es.insert({i, j});
            }
        }
    }

    if (cnt % 2 == 1) {
        cout << "First" << endl;
        cout << flush;
        auto it = es.begin();
        cout << it->first + 1 << ' ' << it->second + 1 << endl;
        cout << flush;
        es.erase(it);
    } else {
        cout << "Second" << endl;
        cout << flush;
    }

    while (es.size()) {
        int u, v;
        cin >> u >> v;
        u--, v--;
        if (u > v)
            swap(u, v);
        es.erase({u, v});

        auto it = es.begin();
        auto [nu, nv] = *it;
        es.erase(it);
        cout << nu + 1 << ' ' << nv + 1 << endl;
        cout << flush;
    }

    int u, v;
    cin >> u >> v;
    if (u == -1 && v == -1) {
        return;
    }
}

F. ABCBA

https://atcoder.jp/contests/abc398/tasks/abc398_f

ローリングハッシュを使って解いた。

$S$ を逆転させたものを $T$ とする。 $S$ の suffix $m$ 文字と $T$ の prefix $m$ 文字が一致する最大の $m$ を求める。

ll modpow(ll x, ll n, ll mod) {
    long long ret = 1;
    while (n > 0) {
        if (n & 1)
            ret = (ret * x) % mod;
        x = (x * x) % mod;
        n >>= 1;
    }
    return ret;
}

struct RollingHash {
    vll data;
    const ll p = 1000000009;
    const ll mod = 1000000021;

    RollingHash(vll& v) {
        int n = v.size();
        data.resize(n + 1);
        rep(i, n) {
            data[i + 1] = (data[i] * p % mod) + v[i];
            data[i + 1] %= mod;
        }
    };

    ll sub(ll l, ll r) {
        ll m = r - l;
        ll x = data[r] - data[l] * modpow(p, m, mod) % mod;
        x += mod;
        x %= mod;
        return x;
    }
};

void solve() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    string s;
    cin >> s;

    string t = s;
    reverse(all(t));

    int n = s.size();
    vll val, rev;
    rep(i, n) {
        val.push_back(s[i] - 'A');
        rev.push_back(t[i] - 'A');
    }

    RollingHash h(val), hr(rev);
    ll common = 0;
    rep(i, n) {
        if (h.sub(n - 1 - i, n) == hr.sub(0, i + 1)) {
            chmax(common, i + 1);
        }
    }
    cout << s;
    cout << t.substr(common, n - common);
    cout << endl;
}

Z Algorithm というのを使うと簡単に解けたらしい。 Z Algorithm は文字列 $T$ に対して T[0:]T[i:] の最長共通接頭辞の長さを求めるアルゴリズム。

$S$ を反転させたものを $S'$ とすると $S' + S$ に対して Z Algorithm を適用。 $z = Zalgorithm(S'+S)$ とすると $z_{n+i} = n-i$ となる最大の $n-i$ が $S$ の suffix と $S'$ の prefix の一致する最大の長さとなる。

$z_{n+i} = n-i$ という条件を外して $\displaystyle \max_{i \in [0,n)} z_{n+i}$ とすると誤答する。

$S = \text{ABXYBA}$ のときを考える。$Zalgorithm(S' + S) = [12, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 1]$ であり、6番目以降の要素を抜き出してくると $[2, 0, 0, 0, 0, 1]$ である。この範囲での最大値は2であるが、これは $S$ の prefix と $S'$ の prefix の一致する文字数なので今回欲しい値ではない。 $S$ の sufiix と $S'$ の prefix の共通する長さを調べるために $z_{n+i} = n-i$ という条件が必要

void solve() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    string s;
    cin >> s;

    int n = s.size();
    string t = s;
    reverse(all(t));
    string concat = t + s;

    vint z = z_algorithm(concat);
    int common = 0;
    rep(i, n) {
        if (z[n + i] == n - i)
            chmax(common, z[n + i]);
    }
    cout << s;
    cout << t.substr(common, n - common);
    cout << endl;
}

G. Not Only Tree Game

https://atcoder.jp/contests/abc398/tasks/abc398_g