洛谷 P3177 [HAOI2015]树上染色 树形DP

洛谷 P3177 [HAOI2015]树上染色 树形DP

题目描述

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

输入格式

第一行包含两个整数 \(n,k\)

第二到 \(n\) 行每行三个正整数 \(fr,to,dis\)表示该树中存在一条长度为 \(dis\) 的边 \((fr, to)\)。输入保证所有点之间是联通的。

输出格式

输出一个正整数,表示收益的最大值。

输入输出样例

输入 #1

3 1
1 2 1
1 3 2

输出 #1

3

说明/提示

对于 \(100\%\) 的数据,\(0≤n,k≤2000\)

分析

很好想的一个树形\(dp\)是设\(f[i][j]\)为当前以\(i\)节点为根的子树中选了\(j\)个黑点所贡献的最大价值

注意状态的定义,是贡献多少而不是总和为多少,因此我们当前只需要考虑新加入的这条边的贡献

即边权乘以两边的白点数量之积+边权乘以两边的黑点数量之积

剩下的做一个树上的背包即可

下面我们来考虑复杂度的问题

递归中有两层循环,看起来似乎是\(n^3\)

但是它的复杂度实际上是\(n^2\)

因为我们递归到某一个点时,枚举的是以这个点为\(LCA\)的所有点对

而每一个点对只有一个\(LCA\)

一个节点数为\(n\)的树最多有\(n^2\)个这样的点对

因此复杂度为\(n^2\)

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
inline int read(){
	int x=0,fh=1;
	char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=2005;
int n,k,tot=1,head[maxn];
struct asd{
	int from,to,next,val;
}b[maxn<<1];
void ad(int aa,int bb,int cc){
	b[tot].from=aa;
	b[tot].to=bb;
	b[tot].next=head[aa];
	b[tot].val=cc;
	head[aa]=tot++;
}
int siz[maxn];
long long f[maxn][maxn];
void dfs(int now,int fa){
	siz[now]=1;
	for(int i=head[now];i!=-1;i=b[i].next){
		int u=b[i].to;
		if(u==fa) continue;
		dfs(u,now);
		for(int j=siz[now];j>=0;j--){
			for(int kk=siz[u];kk>=0;kk--){
				long long nans=1LL*f[now][j]+f[u][kk]+1LL*b[i].val*kk*(k-kk)+1LL*b[i].val*(siz[u]-kk)*((n-k)-(siz[u]-kk));
				f[now][j+kk]=std::max(f[now][j+kk],nans);
			}
		}
		siz[now]+=siz[u];
	}
}
int main(){
	memset(head,-1,sizeof(head));
	n=read(),k=read();
	for(int i=1;i<n;i++){
		int aa,bb,cc;
		aa=read(),bb=read(),cc=read();
		ad(aa,bb,cc),ad(bb,aa,cc);
	}
	dfs(1,0);
	printf("%lld\n",f[1][k]);
	return 0;
}
posted @ 2020-09-03 11:54  liuchanglc  阅读(205)  评论(0编辑  收藏  举报