Official

D - 山岳縦走 / Mountain Traverse Editorial by MMNMM


まず、標高が減少するような登山道を取り除いておきます。

\(\operatorname{dp}[i]\coloneqq{}\)山小屋 \(i\) から出発し、登山道をたどって通ることができる山小屋の個数 \((1\le i\le N)\) を計算することを考えます。 求めたい答えは \(\operatorname{dp}[1]\) です。

これは例えば \(P _ i\) の降順に、次のような計算を行うことで求めることができます。 \[\operatorname{dp}[i]=\begin{cases}1+\max _ {j\in v _ {\mathrm{out}}(i)}\operatorname{dp}[j]&(v _ {\mathrm{out}}(i)\ne\emptyset)\\1&(v _ {\mathrm{out}}(i)=\emptyset)\end{cases}\] ここで、\(v _ {\mathrm{out}}(i)\) は山小屋 \(i\) からの登山道が存在する山小屋の番号の集合です。

更新の順序を \(P _ i\) に対するソートで決めると全体で \(O(N\log N+M)\) 時間、トポロジカルソートを行うと全体で \(O(N+M)\) 時間でこの問題を解くことができます。

実装例は以下のようになります。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int N, M;
    cin >> N >> M;

    vector<int> P(N);
    for (int& p : P) {
        cin >> p;
    }

    vector<vector<int>> edges(N);
    for (int i = 0; i < M; ++i) {
        int u, v;
        cin >> u >> v;
        --u; // 0-indexed にする
        --v;
        if (P[u] < P[v]) { // 標高が増えるような辺だけ追加する
            edges[u].emplace_back(v);
        }
    }

    vector<int> dp_order(N);
    for (int i = 0; i < N; ++i) {
        dp_order[i] = i;
    }
    ranges::sort(dp_order, greater{}, [&P](int i) { return P[i]; }); // P の降順になるようにソートする

    vector<int> dp(N, 1);
    for (int i : dp_order) { // P の降順で DP テーブルを更新
        for (int j : edges[i]) {
            dp[i] = max(dp[i], 1 + dp[j]);
        }
    }

    cout << dp[0] << endl;
    return 0;
}
N, M = map(int, input().split())

P = list(map(int, input().split()))

edges = [[] for _ in range(N)]
for i in range(M):
    u, v = map(int, input().split())
    u -=1 # 0-indexed にする
    v -= 1
    if P[u] < P[v]: # 標高が増えるような辺だけ追加する
        edges[u].append(v)

dp_order = sorted(range(N), reverse=True, key=lambda x: P[x]) # P の降順になるようにソートする

dp = [1 for _ in range(N)]
for i in dp_order: # P の降順で DP テーブルを更新する
    for j in edges[i]:
        dp[i] = max(dp[i], 1 + dp[j])

print(dp[0])

posted:
last update: