[BZOJ 4033] [HAOI2015] T1 【树形DP】
题目链接:BZOJ - 4033
题目分析
使用树形DP,用 f[i][j] 表示在以 i 为根的子树,有 j 个黑点的最大权值。
这个权值指的是,这个子树内部的点对间距离的贡献,以及 i 和 Father[i] 之间的边对答案的贡献(比如这条边对黑点对距离和的贡献就是子树内部的黑点数 * 子树外部的黑点数 * 这条边的权值)。
然后DFS来求,枚举 i 的每个儿子 j,现在的 f[i][] 是包含了 [1, j-1] 子树,然后两重循环枚举范围是 [1, j - 1] 的子树总 Size 和 j 的 Size,来更新 f[i][],这样更新之后的 f[i][] 就是 [1, j] 子树的答案了。
这样的更新看起来是 O(n^3) 的,但是其实可以看做枚举了任意点对的LCA,所以复杂度其实是 O(n^2) 的。
代码
#include <iostream> #include <cstdlib> #include <cstring> #include <cmath> #include <cstdio> #include <algorithm> using namespace std; typedef long long LL; inline int gmin(int a, int b) {return a < b ? a : b;} inline LL gmax(LL a, LL b) {return a > b ? a : b;} const int MaxN = 2000 + 5; int n, k; int Father[MaxN], Size[MaxN]; struct Edge { int v, w; Edge *Next; } E[MaxN * 2], *P = E, *Point[MaxN]; inline void AddEdge(int x, int y, int z) { ++P; P -> v = y; P -> w = z; P -> Next = Point[x]; Point[x] = P; } LL Ans; LL Temp[MaxN], f[MaxN][MaxN]; void Solve(int x, int Fa, int Num) { Size[x] = 1; for (Edge *j = Point[x]; j; j = j -> Next) { if (j -> v == Fa) continue; Solve(j -> v, x, j -> w); for (int i = 0; i <= gmin(Size[x], k); ++i) Temp[i] = f[x][i]; for (int p = 0; p <= gmin(Size[x], k); ++p) for (int q = 0; q <= gmin(Size[j -> v], k); ++q) f[x][p + q] = gmax(f[x][p + q], Temp[p] + f[j -> v][q]); Size[x] += Size[j -> v]; } for (int i = 0; i <= gmin(Size[x], k); ++i) f[x][i] += (LL)Num * (LL)(i * (k - i) + (Size[x] - i) * (n - Size[x] - k + i)); } int main() { scanf("%d%d", &n, &k); int x, y, z; for (int i = 1; i <= n - 1; ++i) { scanf("%d%d%d", &x, &y, &z); AddEdge(x, y, z); AddEdge(y, x, z); } Solve(1, 0, 0); cout << f[1][k] << endl; return 0; }