Official

F - Hands on Ring (Hard) Editorial by yuto1115

解説

動的計画法を用いて解くことを考えます。指示 \(i\) まで従い終えた段階では手 \(H_i\) の位置がパーツ \(T_i\) で確定しており、もう一方の手の位置にだけ自由度があるので、以下のように DP を定義することができます。

\(\mathrm{dp}_{i,j}:=\) 指示 \(i\) まで従い終えて、\(H_i\) でない方の手の位置がパーツ \(j\) であるときの、ここまでの操作回数の合計の最小値 (\(1\leq i\leq Q, 1\leq j\leq N\))

遷移について考えます。今、左手の位置がパーツ \(l\)、右手の位置がパーツ \(r\) であり、これから左手をパーツ \(l'\) に移動させたいと仮定します。右手も左手も自由に動かせるため一見多くの移動パターンがあるように思われますが、実は以下の \(2\) パターン以外は最適でないので考える必要がありません。

  1. 右手を動かさずに左手だけを動かす(B 問題 で行う操作と同じ)。ただし \(l'\neq r\) の場合に限る。
  2. 1 とは逆方向に左手を動かす。そのままだと途中で右手とぶつかってしまうので、左手をパーツ \(l'\) まで動かすために必要な最低限の回数だけ右手も一緒に動かす。すなわち、左手をパーツ \(l\rightarrow l+1\rightarrow \dots\rightarrow l'\) と動かすならば右手はパーツ \(r\rightarrow r+1\rightarrow \dots\rightarrow l'+1\) と動かし、左手をパーツ \(l\rightarrow l-1\rightarrow \dots\rightarrow l'\) と動かすならば右手はパーツ \(r\rightarrow r-1\rightarrow \dots\rightarrow l'-1\) と動かす(なお、パーツ \(N+1\) はパーツ \(1\) を、パーツ \(0\) はパーツ \(N\) をそれぞれ意味するものとする)。

これにより遷移が \(O(1)\) となり、\(O(NQ)\) で本問題を解くことができます。場合分けや円環上での処理が必要になるので、細部に注意しながら実装してください。

実装例 (C++) :

#include <bits/stdc++.h>

using namespace std;

const int inf = 1e9;

vector<pair<int, int>> num_move(int n, int from, int to, int ng) {
    vector<pair<int, int>> res;
    if (to != ng) {
        if (min(from, to) < ng and ng < max(from, to)) {
            res.emplace_back(n - abs(from - to), ng);
        } else {
            res.emplace_back(abs(from - to), ng);
        }
    }
    if (from < ng) {
        if (from < to and to <= ng) {
            res.emplace_back(n - (to - from) + (ng - to + 1), to - 1);
        }
        if (to < from or ng <= to) {
            res.emplace_back((to - from + n) % n + (to - ng + n) % n + 1, (to + 1) % n);
        }
    } else {
        if (ng <= to and to < from) {
            res.emplace_back(n - (from - to) + (to - ng + 1), to + 1);
        }
        if (to <= ng or from < to) {
            res.emplace_back((from - to + n) % n + (ng - to + n) % n + 1, (to - 1 + n) % n);
        }
    }
    return res;
}

int main() {
    int n, q;
    cin >> n >> q;
    vector dp(q + 1, vector<int>(n, inf));
    int ph = 0, pt = 0;
    dp[0][1] = 0;
    for (int i = 0; i < q; i++) {
        char hc;
        int t;
        cin >> hc >> t;
        --t;
        int h = (hc == 'L' ? 0 : 1);
        for (int j = 0; j < n; j++) {
            if (dp[i][j] == inf) continue;
            vector<int> pos(2);
            pos[ph] = pt;
            pos[ph ^ 1] = j;
            for (auto [num, npos]: num_move(n, pos[h], t, pos[h ^ 1])) {
                dp[i + 1][npos] = min(dp[i + 1][npos], dp[i][j] + num);
            }
        }
        ph = h, pt = t;
    }
    int ans = inf;
    for (int i = 0; i < n; i++) ans = min(ans, dp[q][i]);
    cout << ans << endl;
}

posted:
last update: