【洛谷P3177】树上染色

题目

题目链接:https://www.luogu.com.cn/problem/P3177
有一棵点数为 \(n\) 的树,树边有边权。给你一个在 \(0 \sim n\) 之内的正整数 \(m\) ,你要在这棵树中选择 \(m\) 个点,将其染成黑色,并将其他 的 \(n-m\) 个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
\(n,m\leq 2000\)

思路

显然树形 dp。设 \(f[x][i]\) 表示点 \(x\) 为根的子树内选择了 \(i\) 个黑点的最大贡献。
此时如果考虑分类 \(x\) 是黑色或白色再计算 \(x\) 的贡献显然是不可取的,因为点的贡献就涉及到其他相同颜色的点,而在 dp 状态中我们无法把其他点给加进来。
那么考虑计算每一条边的贡献。这样有一个好处:我们不需要知道这条边两边的点的具体位置,我们只需要知道两边分别有多少点,乘法原理计算即可。
考虑加入 \(x\) 的一棵子树 \(y\),有转移

\[f[x][i+j]\gets \max\left (f'[x][i]+f[y][j]+\text{dis}(i,j)\times (j\times (m-j)+(\text{siz}[y]-j)\times (n-m-\text{siz}[y]+j))\right ) \]

发现这个转移第二维上界为子树大小,那么枚举到子树大小就可以做到 \(O(n^2)\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=2010;
int n,m,tot,head[N],siz[N];
ll f[N][N],g[N];

struct edge
{
	int next,to,dis;
}e[N*2];

void add(int from,int to,int dis)
{
	e[++tot]=(edge){head[from],to,dis};
	head[from]=tot;
}

void dfs(int x,int fa)
{
	siz[x]=1;
	for (int k=head[x];~k;k=e[k].next)
	{
		int v=e[k].to;
		if (v!=fa)
		{
			dfs(v,x);
			for (int i=0;i<=siz[x];i++)
				g[i]=f[x][i],f[x][i]=0;
			for (int i=0;i<=min(siz[x],m);i++)
				for (int j=0;j<=siz[v] && i+j<=m;j++)
					f[x][i+j]=max(f[x][i+j],g[i]+f[v][j]+e[k].dis*(1LL*j*(m-j)+1LL*(siz[v]-j)*(n-m-siz[v]+j)));
			siz[x]+=siz[v];
		}
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for (int i=1,x,y,z;i<n;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z); add(y,x,z);
	}
	dfs(1,0);
	printf("%lld",f[1][m]);
	return 0;
}
posted @ 2021-05-20 10:31  stoorz  阅读(128)  评论(0编辑  收藏  举报