返回顶部

树形DP

树形DP

树状动态规划定义

之所以这样命名树规,是因为树形DP的这一特殊性:没有环,dfs是不会重复,而且具有明显而又有严格的层数关系。利用这一特性,我们可以很清晰地根据题目写出一个在树(型结构)上的记忆化搜索的程序。而深搜的特点,就是“不撞南墙不回头” --PPT

一.小胖守皇宫

代码如下:

#include<bits/stdc++.h>
const int N=1550,INF=0x7fffffff;
using namespace std;
int n,st,f[N][3],u[N];
struct stu{
       int size,w,ch[50];
}s[N];
void dp(int x){
     int i,j;
     if(!s[x].size){
        f[x][1]=f[x][2]=s[x].w;
        f[x][0]=0;
        return;
     }
     for(int i=1;i<=s[x].size;i++)dp(s[x].ch[i]);

     f[x][2]=INF;

     for(j=1;j<=s[x].size;j++)f[x][0]+=min(f[s[x].ch[j]][1],f[s[x].ch[j]][2]); 

     for(j=1;j<=s[x].size;j++)f[x][2]=min(f[x][2],f[x][0]-min(f[s[x].ch[j]][1],f[s[x].ch[j]][2])+f[s[x].ch[j]][1]);


     for(j=1;j<=s[x].size;j++)f[x][1]+=f[s[x].ch[j]][0];

     f[x][0]=min(f[x][0],f[x][1]);
}
int main(){
    int i,j,k;
    int x,y,z;
    cin>>n;
    for(i=1;i<=n;i++){
        scanf("%d",&x);
        scanf("%d%d",&s[x].w,&s[x].size);
        for(j=1;j<=s[x].size;j++){
            scanf("%d",&s[x].ch[j]);
            u[s[x].ch[j]]=1;
        }
    }
    for(i=1;i<=n;i++)
        if(!u[i]){
           st=i;
           break;
        }
    memset(u,0,sizeof(u));
    dp(st);
    printf("%d",min(f[st][1],f[st][2]));
    return 0;
}

该题可以分三种情况,一个点可以被父亲守,又可以自己守,还可以让儿子守。
让父亲守时,可选择让儿子守自己,也可以选择让自己守儿子,此时儿子也可以选择守或不守。
自己守时,父亲和儿子都可以选择性地守护。
让儿子守时,情况和让父亲守相仿,自己可守可不守,父亲可守可不守,但自己不守时,儿子和父亲必须被守护。

由此可以推出状态转移方程:
f[x][2]=min(f[x][2],f[x][0]-min(f[s[x].ch[j]][1],f[s[x].ch[j]][2])+f[s[x].ch[j]][1])
f[x][0]+=min(f[s[x].ch[j]][1],f[s[x].ch[j]][2])
f[x][1]+=f[s[x].ch[j]][0]

该题主要难点在于状态的分析和状态转移方程的列出。

二.选课

题干说在选一门课之前要先选其先修课,先修课为0的可以直接选修,则可以把先修课作为父亲,这门课作为儿子,再进行DP。

用f[i][j]表示以i节点为根,选择j个子叶的最大值

可以推出状态转移方程:

f[x][j]=max(f[x][j],f[x][j-1-k]+f[y][k]+s[i].w)
其中k为决策阶段,原理就是把一颗树拆成数棵子树,以减小问题大小。

代码如下:

点击查看代码
#include<bits/stdc++.h> 
using namespace std;
const int N=105;
struct stu{
       int to,next,w;
}s[N*2];
int h[N],t;
void add(int x,int y,int z){
         s[t].to=y;
         s[t].w=z;
         s[t].next=h[x];
         h[x]=t++;
}
int n,m,x,i,y,z;
int f[N][N];
void dfs(int x,int fa){
         for(int i=h[x];~i;i=s[i].next){
             int y=s[i].to;
             if(y==fa)continue;
             dfs(y,x);//先解决子问题,再解决当前问题
             for(int j=m;j>=0;j--){
                 for(int k=0;k<j;k++){
	                 f[x][j]=max(f[x][j],f[x][j-1-k]+f[y][k]+s[i].w);
                 }
             }
         }
         return ;
}
int main(){
    memset(h,-1,sizeof(h));
    cin>>n>>m;
    for(i=1;i<=n;i++){
        cin>>y>>z;
        add(i,y,z);
        add(y,i,z);
    }
    dfs(0,-1);	
    cout<<f[0][m];
    return 0;
}

三.二叉苹果树

这道题与选课有异曲同工之妙,都是若不选父节点,子节点就不能选,若是选了父节点,子节点可选可不选,也就是分成两颗子树进行分析。

唯一的区别就是输入不同,都需要建一个虚根(我自己这么叫的,就是不存在的根)。

代码我就不展示了,改个输入即可。

还有一个非常像的题目,我就把链接放在下面了。

点这里 <--

posted @ 2024-02-17 20:37  无敌の暗黑魔王  阅读(14)  评论(0)    收藏  举报