第一步 复杂度分析
由于 n 的级别是100的,所以可以往O(n^3)上面想,数组可以开到三维。
第二步 类比
类比普通的树龟,我们通常设F[i][j]表示在以 i 为根的子树上选取 j 个结点的最优解。但这道题并不同于普通的树龟。所以需要寻找它与普通树龟的根本差别。
可以发现这道题多了一个DP影响因素:结点 i 上方的伐木场。由于 i 上方的伐木场是不确定的,因此设F[i][j]有后效性。
第三步 化繁为简
既然 i 上方的伐木场会影响DP的值,我们就把它加入维度,该结点称为‘相对祖先’,是从 i 向上走第一个遇到的有伐木场的结点。
设F[i][j][k]表示在以 i 为根的子树上(其相对祖先为结点 j)选取 k 个结点建设伐木场的最小花费(这个最小花费统计到相对祖先为止)。
而结点 i 上是否建有伐木场会影响 i 到 j 这段距离是否加入计算,于是把 F 一分为二,用 f[i][j][k] 表示 i 处无伐木场,g[i][j][k] 表示 i 处建有伐木场。
这样,就基本转化成了普通树龟的题型。
第四步 分类讨论列方程
f: 若不在 i 处建伐木场, 则 f[i][j][k] 为 i 的 k 个儿子到 j(儿子以 j 为相对祖先)的费用, 加上 i 到 j 的费用;
g: 若在 i 处建伐木场, 则 g[i][j][k] 为 i 的 k 个儿子到 i (儿子以 i 为相对祖先)的费用;
F = min(f, g).
第五步 敲代码
多叉树做法
1 #include<cstdio> 2 #include<string> 3 4 const int N = 100 + 10; 5 int n, m, w[N], d[N], head[N], next[N], sum[N], sta[N], top, f[N][N][60], g[N][N][60]; 6 7 int min(int x, int y) { 8 if (x <= y) return x; 9 return y; 10 } 11 12 int read() { 13 int x = 0, f = 1; 14 char c = getchar(); 15 while (!isdigit(c)) { 16 if (c == ‘-‘) f = -1; 17 c = getchar(); 18 } 19 while (isdigit(c)) { 20 x = (x << 3) + (x << 1) + (c ^ 48); 21 c = getchar(); 22 } 23 return x * f; 24 } 25 26 void DP(int u) { 27 sta[++top] = u; 28 for (int i = head[u]; i; i = next[i]) { 29 sum[i] = sum[u] + d[i]; 30 DP(i); 31 for (int a = 1; a <= top; ++ a) // ancient 32 for (int j = m; j >= 0; -- j) { 33 f[u][sta[a]][j] += f[i][sta[a]][0]; 34 g[u][sta[a]][j] += f[i][u][0]; 35 for (int k = 0; k <= j; ++ k) { 36 f[u][sta[a]][j] = min(f[u][sta[a]][j], f[i][sta[a]][k] + f[u][sta[a]][j - k]); 37 g[u][sta[a]][j] = min(g[u][sta[a]][j], f[i][u][k] + g[u][sta[a]][j - k]); 38 } 39 } 40 } 41 for (int a = 1; a <= top; ++ a) 42 for (int j = 0; j <= m; ++ j) 43 if (j == 0) 44 f[u][sta[a]][j] += w[u] * (sum[u] - sum[sta[a]]); 45 else 46 f[u][sta[a]][j] = min(f[u][sta[a]][j] + w[u] * (sum[u] - sum[sta[a]]), g[u][sta[a]][j - 1]); 47 sta[top--] = 0; 48 } 49 50 int main() { 51 n = read(), m = read(); 52 for (int i = 1, v; i <= n; ++ i) { 53 w[i] = read(), v = read(), d[i] = read(); 54 next[i] = head[v], head[v] = i; 55 } 56 DP(0); 57 printf("%d\n", f[0][0][m]); 58 return 0; 59 }
转二叉树做法