[HAOI2015]树上染色

题意

给一棵(n)个点的边带权的树,要求选(k)个点染成白色,其他点为黑色,最大化(黑点两两距离+白点两两距离),((nleq 2000))

思路

一道好题,思路不容易get,但是想出来之后很简单

一般树形DP的套路:设(f_{rt,i})表示以(rt)为根的子树选(i)个点染成白色,这棵子树白点两两距离和;然后发现不会转移了qwq

为什么这样做不行呢?因为这棵子树白点的贡献并不仅限于这棵子树,也就是说这棵子树中的白点需要和子树外的白点统计距离,但是状态的定义使得我们只能统计子树内的白点两两距离,合并状态的时候由于不知道白点的分布而不能求子树间白点的贡献


考虑边的贡献,即可完美解决这个问题

一条边的贡献为:(wi imes) (() 一边的白点数( imes)另一边的白点数(+)一边的黑点数( imes)另一边的黑点数()),于是设(f_{rt,i})表示以(rt)为根的子树中选择(i)个点染为白色时,这棵子树中所有边的贡献

可以发现转移就是个树形背包,于是就这么搞就完事了

Code

#include<bits/stdc++.h>
#define N 2005
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
using namespace std;
typedef long long ll;
int n,m,size[N];
ll f[N][N];
struct Edge
{
	int next,to;
	ll dis;
}edge[N<<1];int head[N],cnt=1;
void add_edge(int from,int to,ll dis)
{
	edge[++cnt].next=head[from];
	edge[cnt].to=to;
	edge[cnt].dis=dis;
	head[from]=cnt;
}

template <class T>
void read(T &x)
{
	char c; int sign=1;
	while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
	while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void dfs(int rt,int fa)
{
	size[rt]=1;
	f[rt][0]=f[rt][1]=0;
	for(int i=head[rt];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		dfs(v,rt);
		size[rt]+=size[v];
		for(int j=Min(size[rt],m);j>=0;--j)
		{
			for(int k=0;k<=Min(j,size[v]);++k)
			{
				ll val = edge[i].dis*((ll)k*(m-k) + (ll)(size[v]-k)*(n-m-size[v]+k));
				f[rt][j]=Max(f[rt][j] , f[v][k] + f[rt][j-k] + val);
			}
		}
	}
}
int main()
{
	read(n);read(m);
	for(int i=1;i<n;++i)
	{
		int x,y; ll z;
		read(x);read(y);read(z);
		add_edge(x,y,z);
		add_edge(y,x,z);
	}
	memset(f,-50,sizeof(f));
	dfs(1,0);
	cout<<f[1][m]<<endl;
	return 0;
}
原文地址:https://www.cnblogs.com/Chtholly/p/11789305.html