其他分享
首页 > 其他分享> > 树上染色

树上染色

作者:互联网

题面

树上染色

题解

这道题转移应该很容易。
直接枚举当前节点染黑的个数以及子节点染黑的个数即可。
设 \(f[x][j]\) 表示以 \(x\) 为根节点的子树中染黑 \(j\) 个点时两种颜色两两距离的之和的最大值。
所以有状态转移方程:

\[f[x][j] = min(f[x][j - p] + f[y][p] + val) \]

其中 \(p\) 为以 \(y\) 为根的子树中染黑的个数, \(y\) 是 \(x\) 的子节点。
后面的 \(val\) 表示连通 \(x,y\) 之后新增的距离。
显然增加的距离是由某种颜色在以 \(y\) 为根的子树中,和其子树外的这种颜色产生的。
我们把两个点之间的路径拆成边,那么总答案就是每条边的长度乘上经过次数的和,所以我们可以倒过来把总答案拆成这样,那么 \(val\) 就等于 \(x, y\) 之间的边的长度乘上它被黑点两两经过和白点两两经过的次数和。
而根据乘法原理对于单种颜色,这条边被这种颜色两两经过的次数就是以 \(y\) 为根的子树中这种颜色的个数乘上子树外的颜色个数。

\[val = (1LL * p * (k - p) + 1LL * (siz[y] - p) * (n - k - siz[y] + p)) * w; \]

\(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