首先一个简单的想法是令 \(dp_{i, j}\) 为以 \(i\) 为根的子树内选择了 \(j\) 个黑点的最大收益,但你会发现这个东西无论怎样转移都需要涉及深度和,如果我们记入深度和就不可能通过这道题了。
不妨换一种想法,我们把每条路径的贡献拆分到每条边上去,即我们计算每条边对总答案的贡献。不难发现我们只需要知道一边有多少个黑色的点即可,于是我们可以令 \(dp_{i, j}\) 为以 \(i\) 为根的子树内黑点有 \(j\) 个 \(i\) 子树内所有边的最大贡献。可以发现这样就很好转移了,需要注意一下我们树形背包枚举第二层如果需要枚举 \(k = 0\),那么我们需要提前转移掉,或者顺序转移,因为如果我们先转移出了 \(dp_{i, j}\) 后面又使用 \(dp_{i, j + 0}\) 转移 \(dp_{i, j}\) 就会重复计算贡献。
然而上面这个做法被链卡掉了,标准的树形 \(dp\) 枚举方式其实是这样的,对于每个子树 \(v_i\),第一层我们枚举到 \(\sum\limits_{j = 1} ^ {i - 1} s_{v_j}\),第二层一样枚举到 \(s_{v_i}\),这样复杂度就对了。因为 \(\sum\limits \sum\limits s_{v_i} \times \sum\limits_{j = 1} ^ {i - 1} s_{v_j} = \sum\limits \sum\limits \sum\limits s_{v_i} \times s_{v_j}(v_i \ne v_j)\) 相当于树上点对数量,这当然是 \(n ^ 2\) 级别的。另外,在这种情况时我们的 \(k\) 要倒序枚举,因为如果我们先考虑 \(k = 0\) 那么接下来我们的 \(dp_{i, j}\) 都会被增加上 \(dp_{i, j + 0} + \cdots\) 的贡献,也会重复计算。
#include<bits/stdc++.h>
using namespace std;
#define N 2000 + 5
#define int long long
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define dep(i, l, r) for(int i = r; i >= l; --i)
#define Next(i, u) for(int i = h[u]; i; i = e[i].next)
struct edge{
int v, next, w;
}e[N << 1];
int n, u, v, w, T, tot, s[N], h[N], dp[N][N];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
void add(int u, int v, int w){
e[++tot].v = v, e[tot].w = w, e[tot].next = h[u], h[u] = tot;
e[++tot].v = u, e[tot].w = w, e[tot].next = h[v], h[v] = tot;
}
void dfs(int u, int fa){
s[u] = 1, dp[u][0] = dp[u][1] = 0;
Next(i, u){
int v = e[i].v; if(v == fa) continue;
dfs(v, u);
dep(j, 0, min(T, s[u])) dep(k, 0, min(T - j, s[v])) if(dp[v][k] != -1 && dp[u][j] != -1){
int val = (T - k) * k * e[i].w + (s[v] - k) * (n - s[v] - T + k) * e[i].w;
dp[u][j + k] = max(dp[u][j + k], dp[v][k] + dp[u][j] + val);
}
s[u] += s[v];
}
}
signed main(){
n = read(), T = read(), memset(dp, -1, sizeof(dp));
rep(i, 1, n - 1) u = read(), v = read(), w = read(), add(u, v, w);
dfs(1, 0);
printf("%lld", dp[1][T]);
return 0;
}