VEXOBEN
Vexoben
Jun 18, 2018
It takes 3 minutes to read this article.

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4033

(刷新以获取数学公式)

题意

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

题解

对于一条边,假设去掉它后分成的两个联通块中,黑色节点个数分别为,白色节点个数分别为,则定义它的贡献

定义状态表示以为根的子树,有个点被染成黑色,子树内的边产生的最大贡献和。

转移的时候,将节点的子树一个个加入。设是当前转移到的儿子,转移方程是:

其中是边产生的贡献,它的值为:

这样复杂度看起来是的。但是我们分析一下会发现复杂度为:

对于点对,仅在的时候对答案有贡献,从而复杂度是的。

#include<bits/stdc++.h>
#define LL long long 
using namespace std;
const int N=2050;

int n,m,E;
int fa[N],siz[N],fir[N],nex[N<<1],arr[N<<1];
LL g[N],len[N<<1],f[N][N];

inline void Add_Edge(int x,int y,LL l) {
	nex[++E]=fir[x];
	fir[x]=E; arr[E]=y; len[E]=l;
}

void dfs(int x) {
	siz[x]=1;
	for (int i=fir[x];i;i=nex[i]) {
		if (arr[i]==fa[x]) continue;
		fa[arr[i]]=x; dfs(arr[i]);
		int sx=min(m,siz[x]);
		int sy=min(m,siz[arr[i]]);
		memset(g,0,sizeof(LL)*(m+1));
		for (LL j=0;j<=sx;j++)
			for (LL k=0;k<=sy;k++) {
				if (j+k>m) break;
				LL eval=len[i]*(k*(m-k)+(siz[arr[i]]-k)*(n-m-siz[arr[i]]+k));
				g[j+k]=max(g[j+k],f[x][j]+f[arr[i]][k]+eval);
			}
		for (int j=0;j<=m;j++) f[x][j]=g[j];
		siz[x]+=siz[arr[i]];
	}
}

int main() {
	scanf("%d%d",&n,&m);
	for (int i=1;i<n;i++) {
		int x,y; LL l; scanf("%d%d%lld",&x,&y,&l);
		Add_Edge(x,y,l); Add_Edge(y,x,l);
	}
	dfs(1);
	printf("%lld",f[1][m]);
	return 0;
}