树型DP——例题分析

树型DP,就是在树上进行的DP。由于树固有的递归性质,树型DP一般都是递归进行的。
通过两个经典例题感受一下。
例1:“没有上司的舞会”问题

公司共有n(n≤6000)位员工。公司要举行一个舞会。 为了让到会的每个人不受他的直接上司约束而能玩得开心,公司领 导决定:
如果邀请了某个人,那么一定不会再邀请他的直接的上司,但 该人的上司的上司,上司的上司的上司……都可以邀请。
已知每个人最多有唯一的一个上司。(该模型就是一个树型模型)
公司的每个人参加晚会都能为晚会增添一些气氛,求一个邀请方案, 使气氛值的和最大。

定义状态:
• 定义 f[i][0/1] 表示以 i 为根节点的子树所能得到的最大的气氛值。
• f[i][0] 表示以 i 为子树且未选择 i 号节点;
• f[i][1] 表示以 i 为子树且选择了 i 号节点
• 最终答案就是 max(f[root][0], f[root][1])
状态转移过程:
在这里插入图片描述
• 上司不参加,下属可以参加,也可以不参加。 • 上司参加,下属只能不参加。
• 枚举量x为当前节点i到能直接到达的所有的子节点。
代码:使用了链式前向星建树。

#include<iostream>
#include<cstdio>
#include<cstring>
#define inf 99999999
using namespace std;
int n,m;
int w[6010],d[6010],head[6010],f[6010][5];
struct edge{
 int from,to,w,next;
}e[6010];
int cnt;
void add(int u,int v,int w){
 e[++cnt].from =u;
 e[cnt].to =v;
 e[cnt].w =w;
 e[cnt].next =head[u];
 head[u]=cnt;
}
void solve(int u){
 if(head[u]==0){
  f[u][0]=0;
  f[u][1]=w[u];
  return;
 }
 for(int i=head[u];i;i=e[i].next ){
  int v=e[i].to ;
  if(f[v][0]==0||f[v][1]==0)
  solve(v);
  f[u][0]+=max(f[v][0],f[v][1]);
  f[u][1]+=f[v][0];
 }
 f[u][1]+=w[u];
}
int main(){
 while(cin>>n&&n!=0){
  cnt=0;
  memset(head,0,sizeof(head));
  memset(d,0,sizeof(d));
  memset(f,0,sizeof(f));
  for(int i=1;i<=n;i++)scanf("%d",&w[i]);
  int l,k;
  for(int i=1;i<n;i++){
   scanf("%d%d",&l,&k);
   add(k,l,1);
   d[l]++;
  }
  int root;
  for(int i=1;i<=n;i++)if(d[i]==0){root=i;break;}
  solve(root);
  int ans=-inf;
  ans=max(f[root][0],f[root][1]);
  printf("%d\n",ans);
 }
 return 0;
} 

例2:

有一棵树(n≤1e5),树上每个节点都是一座房子,房子里有人, 树的每条边都有一个权值(距离),所有人都要去别人家玩(通过
最短路径), 有两个要求: • 1. 所有人都要去别人的房子(不能不动) • 2. 不能同时有两个人去同一个房子 • 求这些人移动距离总和的最大值 。
4
1 2 3
2 3 2
4 3 2
ans=18

单独考虑每一条边。 • 利用树的性质,我们可以得知,一条边将一棵树划分为两个部分。 • 假定这条边的权值为w,两侧两个部分的节点个数分别为m1和m2。 • 每一条边最多经过多少次? • 假设一边比另一边少,那么就让这些节点全部到另一边去,这样的 答案一定是最优的。
在这里插入图片描述
怎样获取对于每个边而言的m1和m2?
• 假定这个树是一棵有根树。 • 有根树的每条边一定是父亲->儿子的关系。 • 对于这条边,m1为以儿子为根的子树的节点数量。 • (m2 = n - m1) • m1在递归的过程中不断计算即可。
本题是hdu-4118。因为递归栈不够大,需要手动增大。
代码:


#pragma comment(linker,"/STACK:102400000,102400000")
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int T,n;
long long ans;
int vis[100010],head[100010];
int min(int a,int b){
 return a<b?a:b;
}
struct edge{
 int from,to,w,next;
}e[200010];
int cnt;
void add(int u,int v,int w){
 e[++cnt].from =u;
 e[cnt].to =v;
 e[cnt].w =w;
 e[cnt].next =head[u];
 head[u]=cnt;
}
int dfs(int u){
 int tmp=1;
 if(head[u]==0)return 1;
 for(int i=head[u];i;i=e[i].next ){
  int v=e[i].to ;
  if(!vis[v]){
   vis[v]=1;
   int y=dfs(v);
   tmp+=y;
   ans+=2*e[i].w*min(y,n-y); 
  }
 }
 return tmp;
}
int main(){
 cin>>T;
 for(int t=1;t<=T;t++){
  cnt=0,ans=0;
  memset(head,0,sizeof(head));
  memset(vis,0,sizeof(vis));
  cin>>n;
  int x,y,z;
  for(int i=1;i<n;i++){
   scanf("%d%d%d",&x,&y,&z);
   add(x,y,z);
   add(y,x,z);
  }
  dfs(1);
  printf("Case #%d: %lld\n",t,ans);
 }
 return 0;
 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值