以下记
sze[u]
为
u
的子树大小,
看到
2000
的数据范围,首先猜想到可能复杂度是
O(n2)
。
首先想到的DP方案是:
f[u][i]
为
u
的子树内,将
然而,不难推出这个DP方案存在后效性——转移时,只有知道了子树内选出的黑色节点的深度之和,才能正确地转移。
因此换一个状态定义:
f[u][i]
为
u
的子树内,将
怎么理解这个「贡献」呢?
也就是说,在这个子树内,所有同色的点对的距离之和计入贡献。而如果存在一个同色的点对
v
和
转移,也就是做一次树形背包DP,设当前考虑到
u
的子节点
那么考虑合并
f′[u][i]
和
f[v][j]
得到
f[u][i+j]
。可以想到,在
v
的子树内的
f[u][i+j]=min(f[u][i+j],f′[u][i]+f[v][j]
+(j∗(m−j)+(sze[v]−j)∗(n−m−sze[v]+j))∗val(u,v))
答案仍然是
f[1][m]
。
复杂度:看上去是
O(n3)
,但是
i
的上界只有
代码:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 2005, M = 4005;
int n, m, ecnt, nxt[M], adj[N], go[M], val[M], sze[N];
ll f[N][N], x[N];
void add_edge(int u, int v, int w) {
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; val[ecnt] = w;
}
void dfs(int u, int fu) {
int i, j; sze[u] = 1;
for (int e = adj[u], v; e; e = nxt[e]) {
if ((v = go[e]) == fu) continue; dfs(v, u);
for (i = 0; i <= sze[u] + sze[v]; i++) x[i] = 0;
for (i = 0; i <= sze[u]; i++) for (j = 0; j <= sze[v]; j++) {
if (i + j > m || n - m < sze[v] - j) continue;
ll delta = (1ll * j * (m - j) + 1ll * (sze[v] - j)
* ((n - m) - (sze[v] - j))) * val[e];
x[i + j] = max(x[i + j], f[u][i] + f[v][j] + delta);
}
for (i = 0; i <= sze[u] + sze[v]; i++) f[u][i] = x[i]; sze[u] += sze[v];
}
}
int main() {
int i, x, y, z; n = read(); m = read();
for (i = 1; i < n; i++) x = read(), y = read(),
z = read(), add_edge(x, y, z); dfs(1, 0);
cout << f[1][m] << endl;
return 0;
}