树形dp<2>

没错,我终于来更新树形dp了,当然也有一部分原因是因为上期的树形dp<1>灰常成功。

废话不多说,我们今天来看树形dp(废话)。至于树上背包呢,我就留到树形dp<3>吧。

首先,我看到了一篇树形dp的博客,还不错,给大家分享一下吧。

例题1:没有上司的舞会

这题可以说是一道比较经典的题目了。我们让dp_{i,0/1}表示以i为根的最优解。接下来就有了两种情况:①上司不参加舞会,下属参加,此时dp_{i,0}=\sum max(dp_{x,1},dp_{x,0});②上次参加舞会,下属不参加,此时dp_{i,1}=\sum dp_{x,0}+a_{i}。然后用dfs递归实现就行了。

#include <bits/stdc++.h>
using namespace std;
struct edge{
  	int v;
	int nxt;
}e[maxn];
int n,cnt,ans;
int head[maxn],dp[maxn][2];
bool ok[maxn],vis[maxn];
void add_edge(int u,int v){
  	e[++cnt].v=v;
  	e[cnt].nxt=head[u];
  	head[u]=cnt;
}
void dfs(int u){
  	vis[u]=true;
  	for(int i=head[u];i;i=e[i].nxt){//枚举该结点的每个子结点
    	if(vis[e[i].v])
			continue;
    	dfs(e[i].v);
    	dp[u][1]+=dp[e[i].v][0];
    	dp[u][0]+=max(dp[e[i].v][0],dp[e[i].v][1]);  // 转移方程
  	}
  	return;
}
int main(){
  	cin>>n;
  	for(int i=1;i<=n;i++)
	  	cin>>dp[i][1];
  	for(int i=1;i<n;i++){
    	int u,v;
    	cin>>u>>v;
    	ok[u]=true;
    	add_edge(v,u);
  	}
  	for(int i=1;i<=n;i++){
    	if(!ok[i]){//从根结点开始DFS
      		dfs(i);
      		cout<<max(dp[i][1],dp[i][0])<<endl;
      		return 0;
    	}
    }
}

参考文献:OI-Wiki

别走啊,还没完呢。

例题2:FAR-FarmCraft

建议各位看英文。

首先,我们令dp_{i}表示以i为根节点的子树中(包括节点i)的所有人安装好游戏所需要的时间;令t_{i}表示兜一圈的时间。

注意:dp_{i}可能小于t_{i}

所以我们分别看一下。

dp_{i}-t_{i}\geq 0  :如果管理员回到了起点那些人还没有装完,那么就需要等待 f[i]−g[i]的时间所有人才能安装好。在等待的这段时间我们可以去下一家,以减少所需的总时间。这里我们贪心,让需要等待时间最久的作为第一个访问的节点,这样可以管理员在他的安装时间内将电脑送给其他人。②dp_{i}-t_{i}<0 :随机访问即可,虽然我还是排了序。

结论(安装到第i个点):\sum (t_{j}+2)+1+dp_{i},其中j为比i先遍历到的同一层的节点。But Why?

\sum (t_{j}+2)表示遍历完所有j的子树的节点,每次都回到根节点(所以要+2);+1是因为root->i的哪一步;+dp_{i}是i的子树的装好游戏的时间。

答案取max即可(为dp_{root}=max{ \sum (t_{j}+2)+1+dp_{i} })。

#include <bits/stdc++.h>
using namespace std;
struct edge{
	int to;
	int nxt;
}e[maxn<<1];
int head[maxn],c[maxn],dp[maxn],t[maxn];
int n,cnt;
bool cmp(int x,int y){
	return (dp[x]-t[x])>(dp[y]-t[y]); 
}
void add_edge(int u,int v){
	cnt++;
	e[cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
void dfs(int u,int fa){
	vector<int> wait;
	for(int i=head[u];i;i=e[i].nxt){
		int to=e[i].to;
		if(to==fa)
			continue;
		dfs(to,u);
		wait.push_back(to);
	}
	sort(wait.begin(),wait.end(),cmp);
	for(int i=0;i<wait.size();i++){
		dp[u]=max(dp[u],t[u]+1+dp[wait[i]]);
		t[u]+=t[wait[i]]+2;
	}
	if(c[u]>t[u] && u!=1)
		dp[u]=max(dp[u],c[u]);
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>c[i];
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	cout<<max(dp[1],t[1]+c[1])<<endl;
	return 0;
}

Ok,以上就是本期的全部内容了。我们树形dp<3>——树上背包再见!

温馨提示:本期的代码均有问题,请不要无脑Ctrl C+Ctrl V,看懂了自己写一遍.

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值