A - リレー競技 (Relay) 解説 by seekworser


走る3人のメンバーを決めたとき、この3人の走順をうまく決めて、リレーの記録が最小になるようにするには、最も \(B_i\) の大きい選手が2番手以外で走れば良いことが分かります。

証明 選んだ3人の選手の100メートル走タイムをそれぞれ $A_1, A_2, A_3$、バトンパスの速さをそれぞれ $B_1, B_2, B_3$ とします。一般性を失わず $B_1 \le B_2 \le B_3$ を仮定してよいです。選手3を2番手に配置した場合の記録は $A_1 + A_2 + A_3 + 2 B_3$、それ以外の場合は $A_1 + A_2 + A_3 + B_3 + B_2$ となります。先の仮定より選手3を2番手以外で走らせる場合記録が最小となることが従います。

特に、選んだ3選手を \(B_i\) の昇順に走らせると \(B_i\) の最も大きい選手は3番手に走ることとなり条件を満たします。

したがって、あらかじめ \((A_i, B_i)\)\(B_i\) の昇順にソートし、\(dp_{i, j} = \)\(i\) 番目の選手まで見て、これまで \(j\) 選手選んだときのリレー記録の最小値)とする動的計画法でこの問題を解くことができます。計算量はソートがボトルネックで \(O(N \log N)\) です。

下の実装例では、DPの遷移が必ず小さい方から大きい方に遷移することを考慮して配列を in-place に更新しています。

#include <bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;
int INF = 1 << 29;
template<typename T> bool chmin(T &a, T b) {if (a > b) {a = b; return true;} return false;}

int main() {
    int n; cin >> n;
    vector<pii> ab(n);
    for (int i=0; i<n; i++) cin >> ab[i].first >> ab[i].second;
    sort(ab.begin(), ab.end(), [](const pii &a, const pii &b) -> bool {return a.second < b.second;});
    vector<int> dp(4, INF);
    dp[0] = 0;
    for (int i=0; i<n; i++) {
        auto [a, b] = ab[i];
        chmin(dp[3], dp[2] + a + b);
        chmin(dp[2], dp[1] + a + b);
        chmin(dp[1], dp[0] + a);
    }
    cout << dp[3] << '\n';
}

投稿日時:
最終更新: