树上染色
作者:互联网
题面
题解
这道题转移应该很容易。
直接枚举当前节点染黑的个数以及子节点染黑的个数即可。
设 \(f[x][j]\) 表示以 \(x\) 为根节点的子树中染黑 \(j\) 个点时两种颜色两两距离的之和的最大值。
所以有状态转移方程:
其中 \(p\) 为以 \(y\) 为根的子树中染黑的个数, \(y\) 是 \(x\) 的子节点。
后面的 \(val\) 表示连通 \(x,y\) 之后新增的距离。
显然增加的距离是由某种颜色在以 \(y\) 为根的子树中,和其子树外的这种颜色产生的。
我们把两个点之间的路径拆成边,那么总答案就是每条边的长度乘上经过次数的和,所以我们可以倒过来把总答案拆成这样,那么 \(val\) 就等于 \(x, y\) 之间的边的长度乘上它被黑点两两经过和白点两两经过的次数和。
而根据乘法原理对于单种颜色,这条边被这种颜色两两经过的次数就是以 \(y\) 为根的子树中这种颜色的个数乘上子树外的颜色个数。
\(p\) 的定义和上述的一样,\(k\) 是黑色的总个数,\(w\) 是 \(x,y\) 之间的边的长度,\(siz\) 是子树的大小。
当 \(k\) 大于 \(n\) 的一半时,我们可以把状态改为染白,这两种是等效的。
然而复杂度上限是 \(2e9\),虽然因为子树大小的原因达不到这个上界,但还是可能会被卡,虽然在洛谷上能过,但据说在 BZOJ 上会被卡爆。
代码
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 2e3 + 5;
int n, k, siz[N]; LL f[N][N];
vector < pair < int, int > > to[N];
inline void add(int u, int v, int w) { to[u].push_back(make_pair(v, w)); to[v].push_back(make_pair(u, w)); }
void dfs(int x, int fa) {
siz[x] = 1; f[x][0] = f[x][1] = 0;
for(unsigned int i = 0; i < to[x].size(); i++) {
int y = to[x][i].first, w = to[x][i].second;
if(y == fa) continue;
dfs(y, x); siz[x] += siz[y];
for(int j = min(k, siz[x]); ~j; j--) {
if(f[x][j] != -1)
f[x][j] = f[x][j] + f[y][0] + 1LL * siz[y] * (n - k - siz[y]) * w;
for(int p = min(j, siz[y]); p; p--) {
if(f[x][j - p] == -1) continue;
LL tmp = (1LL * p * (k - p) + 1LL * (siz[y] - p) * (n - k - siz[y] + p)) * w;
f[x][j] = max(f[x][j], f[x][j - p] + f[y][p] + tmp);
}
}
}
}
int main() {
scanf("%d%d", &n, &k);
if(n < (k << 1)) k = n - k;
for(int i = 1, u, v, w; i < n; i++) scanf("%d%d%d", &u, &v, &w), add(u, v, w);
memset(f, -1, sizeof f);
dfs(1, 0);
printf("%lld\n", f[1][k]);
return 0;
}
标签:include,int,染色,个数,1LL,为根,siz,树上 来源: https://www.cnblogs.com/sjzyh/p/15085224.html