树形dp

顾名思义是树上的dp

例1:没有上司的舞会
题意:
有一场舞会,每一个人都有一个快乐值,但不能和上司同时出现在舞会中,问这个舞会的快乐值最大为多少。
每个人都有唯一的上司,除了根节点没有

状态
用dp[i][0] 表示i节点不去时的最优解
用dp[i][1] 表示i节点去时的最优解
状态转移方程
他要是不去,他儿子就可去可不去
dp[u][0]+=max(dp[v][0],dp[v][1]);
他要是去,他儿子就不能去
dp[u][1]+=dp[v][0];

例2:二叉苹果树
有一棵苹果树,苹果树的是一棵二叉树,共N个节点,树节点编号为1~N,编号为1的节点为树根,边可理解为树的分枝,每个分支都长着若干个苹果,现在要要求减去若干个分支,保留M个分支,要求这M个分支的苹果数量最多。

状态
f[u][j]表示在以u为根的子树保留j个分支可以得到的最大苹果数量
状态转移方程

f[u][k]=f[u][k-j]+f[v][j-1]+edge[i].w;

for(int k=m;k>=0;k++){
	for(int j=1;j<=k;j++){
		if(f[u][k]<f[u][k-j]+f[v][j-1]+edge[i].w;)
		f[u][k]=f[u][k-j]+f[v][j-1]+edge[i].w;
	}
}

比如当保留3个分支时(m==3)
那么他可以变成父亲保留2个分支 儿子不保留 加上父亲指向儿子的这条分支
也可以父亲保留1个分支 儿子保留一个分支 加上父亲指向儿子的这条分支
也可以父亲不保留 儿子保留两个分支 加上父亲指向儿子的这条分支 (其实这个实际上没有意义,因为父亲不保留,那么肯定是父亲之后的枝条全砍了,也就不存在儿子还有两个分支的情况了)

void dfs(int u,int fa){
    for(int i=head[u];~i;i=edge[i].next){
        int v=edge[i].to;
        if(v!=fa){
            dfs(v,u);
        for(int k=m;k>=0;k--){
           for(int j=1;j<=k;j++){
             if(dp[u][k]<dp[u][k-j]+dp[v][j-1]+edge[i].w)
                dp[u][k]=dp[u][k-j]+dp[v][j-1]+edge[i].w;
           }
         }
        }
    }
}

例3:Strategic game
一城堡的所有的道路形成一个n个节点的树,如果在一个节点上放上一个士兵,那么和这个节点相连的边就会被看守住,问把所有边看守住最少需要放多少士兵

树的最小点覆盖
在这里插入图片描述
状态
f[x][1]以x为根的子树在x上放置的士兵的最少所需的士兵数目
f[x][0]以x为根的子树x上不放置的士兵的最少所需的士兵数目
状态转移方程
f[x][1] =1 + Σ min(f[i][0],f[i][1])
x上放置的士兵,于是它的儿子们可放可不放!
f[x][0] = Σ f[i][1]
x上不放置的士兵,它的儿子们都必须放!
(i是x的儿子!!)
结果为min(f[root][0], f[root][1])

void dfs(int u,int fa){
    dp[u][1]=1;  //dp[x][1]以x为根的子树在x上放置的士兵的最少所需的士兵数目  但他可能没有儿子,所以先赋值个1
    for(int i=head[u];~i;i=edge[i].next){
        int v=edge[i].to;
        if(v!=fa){
            dfs(v,u);
            dp[u][1]+=min(dp[v][0],dp[v][1]);   //u放 儿子可放可不放
            dp[u][0]+=dp[v][1];  //不放的话  它的儿子都必须放
        }
    }
}

树的最小支配集

给你一棵无向树,问你最少用多少个点可以覆盖掉所有其他的点。
(一个点被盖,它自己和与它相邻的点都算被覆盖)

在这里插入图片描述
状态:
1.dp[u][0]:选点u,并且以点i为根的子树都被覆盖了。
2.dp[u][1]:不选点u,u被其儿子覆盖
3.dp[u][2]:不选点u,u没有被子节点覆盖(被其父亲覆盖)

状态转移方程:
1.dp[u][0]=1+Σmin(dp[v][0],dp[v][1],dp[v][2])
2.dp[u][2]=Σ(dp[v][1]) (他被父亲覆盖,那他的儿子一定是被其儿子覆盖的)
3.dp[u][1]比较复杂(他的所有儿子里面必须有一个取dp[u][1])
那么:if(u没有子节点) dp[u][1]=INF
else dp[u][1]=Σmin(dp[v][0],dp[v][1])+inc
对于inc有:
if(上面式子中的Σmin(dp[v][0],dp[v][1])中包含某个dp[v][0]) inc=0;
else inc=min(dp[v][0]-dp[v][1])

void dfs(int u,int fa){
    dp[u][0] = 1;
    dp[u][1] = 0;
    dp[u][2] = 0;
    int sum = 0, inc = INF;
    bool judge = false;
    for (int i = head[u]; ~i;i=edge[i].next){
        int v = edge[i].v;
        if(v!=fa){
            dfs(v, u);
            dp[u][0] += min(dp[v][0], min(dp[v][1], dp[v][2]));
            if(dp[v][0]<=dp[v][1]){  //存在 取儿子 小于 儿子被其儿子覆盖  
                sum += dp[v][0];
                judge = true;
            }else{
                sum += dp[v][1];
                inc = min(inc, dp[v][0] - dp[v][1];)
            }
            if(dp[v][1]!=INF&&dp[u][2]!=INF)  //当前结点被父亲覆盖,儿子节点都被儿子覆盖,当前结点才能被父亲覆盖
                dp[u][2] += dp[v][1];
             else
             {
                 dp[u][2] = INF;  
             }
             dp[u][1] += min(dp[v][0], dp[v][1]);
        }
    }
    if(inc==INF&&!judge) //u没有儿子
        dp[u][1] = INF;
     else{     
         dp[u][1] = sum;  //inc=0
         if(!judge)
             dp[u][1] += inc; //inc= inc = min(inc, dp[v][0] - dp[v][1];)
     }   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值