【题目链接】
【思路要点】
- 记\(val_i\)表示在点\(i\)处设置存档点,之后不再设置存档点,并完成\(i\)为根的子树中所有剧情的最小代价,\(val\)显然可以通过简单DP得出。
- 一旦我们开始完成一个点的某个子树的剧情,我们显然不会在结束所有剧情前去访问它的其它子树。
- 记\(dp_i\)表示在点\(i\)处设置存档点,之后可以再设置存档点,并完成\(i\)为根的子树中所有剧情的最小代价。
- 考虑如何计算\(dp_i\),除了\(i\)最后一个处理的子树,处理其余子树\(j\)付出的代价或是“不设置存档点的”代价,可以由\(val_j\)得出;或是“设置存档点的代价”,可以由\(dp_j\)得出,并且需要加上重新开始后回到\(i\)处设置存档点的代价。显然应当将两者取最小值作为处理该子树的代价。而\(i\)最后一个处理的子树显然可以设置存档点,并且不用加上重新开始后回到\(i\)处设置存档点的代价。
- 枚举\(i\)最后一个处理的子树即可。
- 时间复杂度\(O(N)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1000005; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct edge {int dest, len; }; vector <edge> a[MAXN]; int father[MAXN], flen[MAXN]; long long dp[MAXN], tmp[MAXN]; long long leaves[MAXN], val[MAXN]; void work(int pos, long long len) { if (a[pos].size() == 0) { leaves[pos] = 1; val[pos] = dp[pos] = 0; return; } long long sum = 0; for (unsigned i = 0; i < a[pos].size(); i++) { int dest = a[pos][i].dest; work(dest, len + a[pos][i].len); leaves[pos] += leaves[dest]; val[pos] += val[dest] + 1ll * leaves[dest] * a[pos][i].len; sum += tmp[dest] = min(val[dest] + 1ll * leaves[dest] * a[pos][i].len, dp[dest] + a[pos][i].len + len); } dp[pos] = val[pos]; for (unsigned i = 0; i < a[pos].size(); i++) { int dest = a[pos][i].dest; chkmin(dp[pos], sum - tmp[dest] + dp[dest] + a[pos][i].len); } } int main() { int n; read(n); for (int i = 1; i <= n; i++) { int k; read(k); while (k--) { int x, y; read(x), read(y); flen[x] = y; father[x] = i; a[i].push_back((edge) {x, y}); } } work(1, 0); writeln(dp[1]); return 0; }