[Luogu] P3177 [HAOI2015]树上染色

\(Link\)

Description

有一棵点数为\(n\)的树,树边有边权。给你一个在\(0 \sim n\)之内的正整数\(k\),你要在这棵树中选择\(k\)个点,将其染成黑色,并将其他的\(n-k\)个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。\((n,k\le2000)\)

Solution

妙妙的树形\(DP\)

我们设\(dp[x][t]\)表示在以\(x\)为根的子树中选择\(t\)个点染黑时的最大贡献。

发现距离不太好搞,那么我们可以将距离转化为路径,然后再将路径拆分成边,就可以记录每条边被经过的次数,直接计算即可。

我们枚举\(x\)的子节点\(y\),先处理\(y\)这棵子树和其他子树,再对\(x\)做贡献。分别枚举\(y\)中被染黑的节点数\(tn\)\(x\)中除了\(y\)的子树被染黑的节点数\(te\)。注意要倒序枚举,避免后效性。那么这样产生的贡献就是\(value=z*(k - tn) * tn + (sz[y] - tn) * (n - k - sz[y] + tn)\)(黑点贡献加上白点贡献)。

总的方程就是\(dp[x][te+tn]=max\{dp[x][te]+dp[y][tn]+value\}\)

注意枚举\(sz[]\)的时候,不要在最开始就一遍算出来,要一边算,一遍处理\(DP\)数组。这样就不会重复算,防止超时。因为\(x\)可能会有多棵子树,我们一棵一棵枚举,现在的\(dp[x][te]\)其实就是之前的\(dp[x][te+tn]\),正好已经算过。

Code

#include <bits/stdc++.h>

using namespace std;

#define ll long long

int n, k, tot, hd[2005], to[4005], nxt[4005], w[4005], sz[2005];

ll dp[2005][2005];

int read()
{
	int x = 0, fl = 1; char ch = getchar();
	while (ch < '0' || ch > '9') { if (ch == '-') fl = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
	return x * fl;
}

void add(int x, int y, int z)
{
	tot ++ ;
	to[tot] = y;
	w[tot] = z;
	nxt[tot] = hd[x];
	hd[x] = tot;
	return;
}

void dfs(int x, int fa)
{
	sz[x] = 1;
	for (int i = hd[x]; i; i = nxt[i])
	{
		int y = to[i], z = w[i];
		if (y == fa) continue;
		dfs(y, x);
		sz[x] += sz[y];
		for (int te = sz[x] - sz[y]; te >= 0; -- te) // 注意是倒序枚举!
			for (int tn = sz[y]; tn >= 0; -- tn)
				dp[x][te + tn] = max(dp[x][te + tn], dp[x][te] + dp[y][tn] + 1ll * z * ((k - tn) * tn + (sz[y] - tn) * (n - k - sz[y] + tn)));
	}
	return;
}

int main()
{
	n = read(); k = read();
	for (int i = 1; i <= n - 1; i ++ )
	{
		int x = read(), y = read(), z = read();
		add(x, y, z); add(y, x, z);
	}
	dfs(1, 0);
	printf("%lld\n", dp[1][k]);
	return 0;
}
posted @ 2020-10-31 17:34  andysj  阅读(57)  评论(0编辑  收藏  举报