Official
D - 山岳縦走 / Mountain Traverse Editorial
by
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:
