关于树上dp的一种思路 洛谷 P3177 [HAOI2015]树上染色

树上最经典的dp题目无疑是树上背包,是基于点的一种状态定义和转移方式,但是这样的思维方式有时候是不可行的,关于边的讨论方式也是一种非常优秀的转移方式,并且此时的状态中存储的往往不是此状态下的价值,而是对整体答案的贡献(应为如果能表示出价值往往可以对点dp来求解),我们通过一道例题来看这个问题。
P3177 [HAOI2015]树上染色
题目大意:有一棵点数为 N 的树,树边有边权。给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 。 将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
n、k<=2000;
题解:很轻松可以发现如果关于每个点及其子树定义状态并转移,我们并不能求出此状态下的权值,如果暴力的讨论图又无疑会T,我们就可以发现如果对于边讨论,每条边对于答案的贡献是经过它路径的条数*边权,同时发现对于一条边,我们已经得知其一端子树的大小,如果枚举子树中的黑点个数,就可以得出其两端分别的黑、白点个数,然后就可以轻易的求出边的贡献,那么我们就有一个非常清晰的状态转移方程:
枚举i、j,分别表示对于本点所有子树和其单一子树的黑点个数,用val表示此边的贡献,就有
dp[u][i] = max( dp[u][i], dp[u][i-j] + dp[v][j] + val )
很轻松就能做出结果。
注:1、在进行树上dp时一定要注意能不能本状态的合法性和转移到本状态的合法性,例如本题转移时要注意一颗子树一颗子树地枚举,且i需要倒叙枚举,原因在于防止由(有本子树的状态)转移到(有本子树的状态),这样无疑是错的,另有例如,由不存在的状态转移到本状态,这样也同样是错的,在本题中体现为dp[u][i-j]状态不一定在以前讨论过,我们可以利用至负无穷或标记的方式来规避出错,写dp时一定要考虑到这些情况,加以解决。
2、注意开LL

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=2e3+10;
typedef long long LL;
struct Edge{
	int to,nxt;LL val;
}e[maxn*2];
LL n,k,head[maxn],cnt,dp[maxn][maxn],size[maxn];
void add(int x,int y,int z)
{
	cnt++;e[cnt].to=y,e[cnt].val=z;
	e[cnt].nxt=head[x],head[x]=cnt;
}
void dfs(int now,int fa)
{
	size[now]=1;
	dp[now][0]=dp[now][1]=0;
	for(int i=head[now];i;i=e[i].nxt){
		int to=e[i].to;
		if(to!=fa){
			dfs(to,now);
			size[now]+=size[to];
		}
	}
	for(int i=head[now];i;i=e[i].nxt)
		for(LL j=min(size[now],k);j>=0;j--)
			if(e[i].to!=fa){
				int to=e[i].to;LL val=e[i].val;
				for(LL w=0;w<=min(size[to],j);w++){
					if(dp[now][j-w]!=-1){
						LL val_=w*(k-w)*val+(size[to]-w)*(n-size[to]-(k-w))*val;
						dp[now][j]=max(dp[now][j],dp[now][j-w]+dp[to][w]+val_);
					}
				}
			}
}
int main()
{
	//freopen("3177.txt","r",stdin);
	LL x,y,z;
	scanf("%lld%lld",&n,&k);
	for(int i=1;i<n;i++){
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z),add(y,x,z);
	}
	memset(dp,-1,sizeof(dp));
	dfs(1,0);
	printf("%lld",dp[1][k]);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值