【BZOJ4033】[HAOI2015] 树上染色(树形DP)

点此看题面

大致题意: 给你一棵点数为N的带权树,要你在这棵树中选择K个点染成黑色,并将其他的N-K个点染成白色。要求你求出黑点两两之间的距离加上白点两两之间距离的和的最大值。


树形 D P DP DP

这道题应该是一道比较显然的树形 D P DP DP,我们可以用f[x][i]来表示当前节点为x时有i个黑色节点时能取得的最大值。则转移方程应为(伪代码)

f[x][i]=max(f[x][i],f[x][i-j]+f[x的一个子节点][j]+j*(m-j)*x与这个子节点之间边的边权+1LL*(Size[x的子节点]-j)*(n-m+j-Size[x的子节点])*边的边权);

既然推出了转移方程,那么这题就好做了,只要先用dfs预处理,再DP就可以了。


代码
#include<bits/stdc++.h>
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define LL long long
#define N 2000 
using namespace std; 
int n,m,ee=0,lnk[N+5],fa[N+5],vis[N+5],Size[N+5];
LL f[N+5][N+5];
struct edge
{
	int to,nxt,val;
}e[2*N+5];
inline char tc()
{
	static char ff[100000],*A=ff,*B=ff;
	return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++; 
}
inline void read(int &x)
{
	x=0;int f=1;char ch;
	while(!isdigit(ch=tc())) if(ch=='-') f=-1;
	while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
	x*=f;
}
inline void write(LL x)
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
inline void add(int x,int y,int z)
{
	e[++ee]=(edge){y,lnk[x],z},lnk[x]=ee;
}
inline int dfs(int x)//dfs预处理出每个节点的父亲以及子树大小
{
	register int i;
	for(Size[x]=1,i=lnk[x];i;i=e[i].nxt)
		if(e[i].to^fa[x]) fa[e[i].to]=x,Size[x]+=dfs(e[i].to);
	return Size[x];
}
inline void dp(int x)//DP的核心内容
{
	vis[x]=1;//标记已访问
	register int y,i,j;
	for(y=lnk[x];y;y=e[y].nxt)
	{
		if(vis[e[y].to]) continue;
		dp(e[y].to);
		for(i=min(Size[x],m);i>=0;--i)
			for(j=0;j<=min(Size[e[y].to],i);++j)
				f[x][i]=max(f[x][i],f[x][i-j]+f[e[y].to][j]+1LL*j*(m-j)*e[y].val+1LL*(Size[e[y].to]-j)*(n-m+j-Size[e[y].to])*e[y].val);//转移方程
	}
}
int main()
{
	freopen("a.in","r",stdin);
	register int i;int x,y,z;
	for(read(n),read(m),i=1;i<n;++i) 
		read(x),read(y),read(z),add(x,y,z),add(y,x,z);
	dfs(1);
	2*m>n?m=n-m:0;//一个非常重要的优化,没有可能会TLE,原理在于在树上将m个节点染成黑色与将n-m个节点染成黑色其实是等价的
	memset(f,167,sizeof(f));
	for(i=1;i<=n;++i) 
		f[i][0]=f[i][1]=0;
	return dp(1),write(f[1][m]),0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值