树形DP

学了树形DP的一点心得。。。虽然可能没啥意义。。。

树形dp大概就是在树上做dp吧。

遍历树上节点的同时,将一些数值通过转移方程传递给下(上)一个状态,大概分成两种状态:

1.由儿子向父亲(叶——》)根;

2.由父亲到儿子(根——》叶);

一般都是儿子到父亲(笔者是个蒟蒻,还没见过2。。。)

因为我们要处理一个节点时,要从父亲(或所有儿子)得到值,又要把值给所有儿子或父亲,

线性不太好实现,然而DFS的顺序刚好符合要求,于是

DFS顺序遍历是解决树上DP的常见方法

 1 void tdp(int x,int fa){
 2     for(int i=head[x];i;i=q[i].nex){
 3         int j=q[i].to;
 4         if(j!=fa){
 5             tdp(j,x);
 6             //(转移)
 7         }
 8     }
 9 }

链表的DFS

当然,这不是绝对的,线性也有符合情况的,因为拓扑序也代表了先后顺序,可以用拓扑序倒序来代替后续便利。

因为懒,所以没有代码。。。

 

对于树形DP我们一般先确定状态,大概是问什么设什么:

 

问题 A: 二叉苹果树

题目描述

  
题上的提问,问在剩余X树枝后,最多可以留下多少苹果。
以dp的思路来看,我们可以设:
  F[i][j]
用来 表示,我们在以 i 节点为根,选择其中 j 个树枝留下时,最多的苹果数,
以X代表当前结点,Y代表其儿子,j 代表选择了 j 个树枝,t代表当前背包大小。
由此就可以得出状态转移方程式:
 
f[x][t]=max(f[x][t-y]+f[y][y],f[x][t])
 
因为每个树枝的大小为1,而每颗子树都可能包含许多节点,
我们就把由 j 个节点组成的子树看作一个物品,而且其权值已经在遍历这颗子树的儿子是计算过了,
我们可以直接调用。
关于赋值问题,f[i][1]=val[i];
因为每个节点为根,只装一个的最大值就是他本身。
 
代码。。。
#include<bits/stdc++.h>
using namespace std;
int head[150],val[150],n,m,tt=0,f[150][150],mp[105][105],l[105],r[105];
 
void biuldtree(int x){
    for(int i=1;i<=n;i++){
        if(mp[x][i]>=0){
            l[x]=i;val[i]=mp[x][i];
            mp[x][i]=mp[i][x]=-1;
            biuldtree(i);
            break;      
        }
 
    }
    for(int i=1;i<=n;i++){
        if(mp[x][i]>=0){
            r[x]=i;val[i]=mp[x][i];
            mp[x][i]=mp[i][x]=-1;
            biuldtree(i);
            break;
        }
    }
    return;
}
 
int tdp(int i,int j){
    if(j==0)return 0;
    if(!l[i]&&!r[i])return val[i];
    if(f[i][j])return f[i][j];
    for(int k=0;k<=j-1;k++)
        f[i][j]=max(f[i][j],tdp(l[i],k)+tdp(r[i],j-k-1)+val[i]);
    return f[i][j];
}
   
int main(){
    cin>>n>>m;
    int a,b,k;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            mp[i][j]=-1;
        }
    }
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&a,&b,&k);
        mp[a][b]=mp[b][a]=k;
    }
    biuldtree(1);
    cout<<tdp(1,m+1);
    return 0;
     
}
View Code

 

同样,有差不多一题:选课

问题 B: 选课

题目描述

大学实行学分制。每门课程都有一定的学分,学生只要选修了这门课并通过考核就能获得相应学分。学生最后的学分是他选修各门课的学分总和。  每个学生都要选择规定数量的课程。有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其他的一些课程基础上才能选修。例如《数据结构》必须在选修了《高级语言程序设计》后才能选修。我们称《高级语言程序设计》是《数据结构》的先修课。每门课的直接先修课最多只有一门。两门课也可能存在相同的先修课。为便于表述,每门课都有一个课号,课号依次为 1,2,3,……n。 

#include<bits/stdc++.h>
using namespace std;
 
int head[5050],nex[5050],to[5050],val[5050],cnt=0,n,m;
int dp[5050][5050];
void add(int a,int b){
    nex[++cnt]=head[a];
    to[cnt]=b;
    head[a]=cnt;
}
 
void tdp(int x){
 
    for(int i=head[x];i;i=nex[i]){
        tdp(to[i]);
         
        for(int t=m;t>=0;t--){
            for(int j=t;j>=0;j--){
                dp[x][t]=max(dp[x][t],dp[x][t-j]+dp[to[i]][j]); 
            }
        }
    }   
    if(x!=0){
        for(int t=m;t>0;t--){
            dp[x][t]=dp[x][t-1]+val[x];
        }
    }
     
}
 
int main(){
    cin>>n>>m;
    int a,b,c;
    for(int i=1;i<=n;i++){
        cin>>a>>c;
        add(a,i);
        val[i]=c;
    }
    tdp(0);
    cout<<dp[0][m]<<" ";
} 
View Code

与二叉苹果树是一样的吧。。。不过根节点是个假的,没有权且选了没有意义,需要特判一下。

 

还有战略游戏和没有上司的舞会

这俩是几乎一毛一样的的。。。

一个max,一个min,相信大家都看得出来。。。

战略游戏

Bob 喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的方法。现在他有个问题。
现在他有座古城堡,古城堡的路形成一棵树。他要在这棵树的节点上放置最少数目的士兵,使得这些士兵能够瞭望到所有的路。
注意:某个士兵在一个节点上时,与该节点相连的所有边都将能被瞭望到。
请你编一个程序,给定一棵树,帮 Bob 计算出他最少要放置的士兵数。

没有上司的舞会

Ural 大学有 N 个职员,编号为 1~N。他们有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。每个职员有一个快乐指数。现在有个周年庆宴会,要求与会职员的快乐指数最大。但是,没有职员愿和直接上司一起与会。

#include<bits/stdc++.h>
using namespace std;
 
struct dd{
    int nex,to,sum;
}q[10050];
 
int cnt=0,val[6050],n,m,tt=0,head[6050],dp[6050][2];
 
bool pp[50500];
 
void add(int a,int b){
    q[++cnt].nex=head[a];
    q[cnt].to=b;
    head[a]=cnt;
}
 
void tdp(int x,int fa){
    dp[x][1]=val[x];
    //zlyx dp[x][1]=1;
    dp[x][0]=0; 
    for(int i=head[x];i;i=q[i].nex){
        int j=q[i].to;
        if(j!=fa){
            tdp(j,x);
            dp[x][0]+=max(dp[j][1],dp[j][0]);
            dp[x][1]+=dp[j][0];
            //战略游戏
            //dp[x][0]+=dp[j][1];
            // dp[x][1]+=min(dp[j][1],dp[j][0]);

          
        }
    }
}
 
int main(){
    memset(dp,0,sizeof(dp));
    cin>>n;
    int a,b,c;
    for(int i=1;i<=n;i++){
        cin>>val[i];
    }
    cin>>a>>b;
    while(a){
        add(a,b);
        add(b,a);
        cin>>a>>b;
    }
    tdp(1,0);
    cout<<max(dp[1][1],dp[1][0]);
//cout<<min(dp[1][1],dp[1][0]);
    return 0;
}
    
View Code

因为没有上司的舞会所有点满足且只能不存在相邻的点被选,不存在先后,互不影响,所以不需要求根节点,因为

所有节点此时都是等价的。

而战略游戏则是所有路被看,也不存在先后,故把1做根就行。

 
皇宫看守

太平王OYWB事件后,LA成了皇上特聘的御前一品侍卫。 皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;有边直接相连的宫殿可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。 可是LA手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。

编程任务:帮助LA布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。

这一题比较有趣,因为他是只有点被看到就行,

所以一个点就有三种情况,

f1:不选,且已经被儿子看到

f2:不选,还没有确定是否被看到

f3:选

易知,f2[i]的情况只需要Σmin(f2[j],f3[j]),因为此时不一定选,所以只需要求最小即可

而f1[i]则是取Σ f2[ j ] -min(f2[j],f3[j]),意为,在所有可能不选的最小方案中,找到一个代价最小的选择,保证满足每个点都有人看。

f3就喜闻乐见咯毕竟选了他,其他子节点选不选都ok咯f3[i]=Σmin{f1[j],f2[j],f3[j]};

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct dd{
    int nex,to,sum;
}q[13050];
 
int cnt=0,val[6050],n,m,tt=0,head[6050],dp[6050][3];
 
void add(int a,int b){
    q[++cnt].nex=head[a];
    q[cnt].to=b;
    head[a]=cnt;
}
 
void tdp(int x,int fa){
    dp[x][1]=0;//不选X,且没人看X 
    dp[x][2]=0;//不选x,且有人看X 
    int tag;int ddp=0x3fffffff;
    for(int i=head[x];i;i=q[i].nex){
        int j=q[i].to;
        if(j!=fa){
            tdp(j,x);
            dp[x][0]+=min(dp[j][1],min(dp[j][2],dp[j][0]));
            dp[x][1]+=min(dp[j][0],dp[j][2]);
            dp[x][2]+=min(dp[j][0],dp[j][2]);
            ddp=min(ddp,dp[j][0]-min(dp[j][0],dp[j][2]));
             
        }
    }
    dp[x][2]+=ddp;
    dp[x][0]+=val[x];//选X 
}
 
int main(){
    memset(dp,0,sizeof(dp));
    cin>>n;
    int a,b,c;
    for(int i=1;i<=n;i++){
        cin>>a>>b>>c;
        val[a]=b;
        for(int j=1;j<=c;j++){ 
            cin>>b;
            add(a,b);
            add(b,a);
        } 
    }
    int i=1;
 
    tdp(1,0);
    cout<<min(dp[1][2],dp[1][0]);
    return 0;
}
View Code

 

然后就这样了,

还有一些例题,大家可以自己思考一下。

偷天换日

神偷对艺术馆内的名画垂涎欲滴准备大捞一把。艺术馆由若干个展览厅和若干条走廊组成。每一条走廊的尽头不是通向一个展览厅,就是分为两个走廊。每个展览厅内都有若干幅画,每副画都有一个价值。经过走廊和偷画都是要耗费时间的。警察会在第n秒到达进口,在不被逮捕的情况下你最多能得到的价值。

加分二茬树(好像用区间。。)

设一个 n 个节点的二叉树 tree 的中序遍历为 (1,2,3,……,n),其中数字 1,2,3,……,n 为节点编号。每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 d_i,tree及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree本身)的加分计算方法如下:
subtree的左子树加分为 l,右子树加分为 r,subtree 的根的分数为 a,则 subtree的加分为: l*r+a
若某个子树为空,规定其加分为 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为 (1,2,3,……,n) 且加分最高的二叉树 tree。
要求输出:

    1. tree 的最高加分;
    2. tree的前序遍历。

 

posted @ 2019-07-29 09:54  you_xiao  阅读(207)  评论(0编辑  收藏  举报