[总结] 树形 dp

树形dp

概念类

树形dp是一种很优美的动态规划,真的很优美真的,前提是在你学会它之后。

实现形式

树形dp的主要实现形式是dfs,在dfs中dp,主要的实现形式是dp[ i ][ j ][ 0/1 ],i是以i为根的子树,j是表示在以i为根的子树中选择j个子节点,0表示这个节点不选,1表示选择这个节点。有的时候j或0/1这一维可以压掉

基本的dp方程

1.选择节点类

dp[i][0]=dp[j][1]dp[i][1]=max/min(dp[j][0],dp[j][1])

2.树形背包类

{dp[v][k]=dp[u][k]+valdp[u][k]=max(dp[u][k],dp[v][k−1])

例题类

以上就是对树形dp的基本介绍,因为树形dp没有基本的形式,然后其也没有固定的做法,一般一种题目有一种做法。

没有上司的舞会传送门

这道题是一树形dp入门级别的题目,具体方程就用到了上述的选择方程。

#include <iostream>
#include <algorithm>
#include <cstdio>
#define re register
using namespace std;
const int maxn=6005;
int head[maxn],cnt=0,n,f[maxn][2],a[maxn],ans=0,vis[maxn];
struct tree{
    int to,nxt;
}e[maxn];
inline void link(int u,int v){
    e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;
}
inline void solve(int u){
    f[u][1]=a[u];f[u][0]=0;
    for(re int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        solve(v);
        f[u][0]+=max(f[v][0],f[v][1]);
        f[u][1]+=f[v][0];
    }
}
int main(){
    scanf("%d",&n);
    for(re int i=1;i<=n;i++)scanf("%d",a+i);
    for(re int i=1;i<=n-1;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        link(v,u);
        vis[u]=true;
    }
    int root;
    for(re int i=1;i<=n;i++)if(!vis[i]){root=i;break;}
    solve(root);
    cout<<max(f[root][1],f[root][0]);
    return 0;
}

最大子树和

这道题的dp方程有变,因为你的操作是切掉这个点,所以你的子树要么加上价值,要么价值为0,所以dp方程是

dp[u]+=max(dp[v],0)

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>

using namespace std;
struct edge
{
    int next,to;
} e[40000];
int head[40000],tot,rt,maxn;
void add(int x,int y)
{
    e[++tot].next=head[x];
    head[x]=tot;
    e[tot].to=y;
}
int n,dp[20000],ind[20000];
int val[20000],f[20000];
void dfs_f__k(int x,int fa)
{
    f[x]=fa;
    for(int i=head[x]; i; i=e[i].next)
    {
        int v=e[i].to;
        if(v!=fa)
            dfs_f__k(v,x);
    }
}
void dfs(int x)
{
    dp[x]=val[x];
    for(int i=head[x]; i; i=e[i].next)
    {
        int v=e[i].to;
        if(v!=f[x])
        {
            dfs(v);
            dp[x]+=max(0,dp[v]);
        }
    }
    maxn=max(maxn,dp[x]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; i++)scanf("%d",&val[i]);
    for(int i=1; i<=n-1; i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    rt=1;
    dfs_f__k(rt,0);
    dfs(rt);
    printf("%d",maxn);
}

选课P2014

这道题的意思是每本书要想选择一门课,必须要先学会它的必修课,所以这就形成了一种依赖行为,即选择一门课必须要选择必修课。那么他又说要选择的价值最大,这就要用到树形背包的知识了。
树形背包的基本代码形式(即上面的树形背包类)

/*
设dp[i][j]表示选择以i为根的子树中j个节点。
u代表当前根节点,tot代表其选择的节点的总额。
*/
void dfs(int u,int tot)
{
	for(int i=head[x];i;i=e[i].next)
	{
		int v=e[i].to;
		for(int k=0;k<tot;k++)//这里k从o开始到tot-1,因为v的子树可以选择的节点是u的子树的节点数减一
			dp[v][k]=dp[u][k]+val[u];
		dfs(v,tot-1)
		for(int k=1;k<=tot;k++)
			dp[u][k]=max(dp[u][k],dp[v][k-1]);//这里是把子树的值赋给了根节点,因为u选择k个点v只能选择k-1个点。
	}
}

然后这就是树形背包的基本形式,基本就是这样做
代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=305,maxm=305;
int head[maxn],w[maxn],f[maxn][maxn],cnt = 0,n,m;
int sz[maxn]; 
struct Tree{
	int to,nxt;
}e[maxm];
inline void link(int u,int v){
	e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;
}
void dp(int x){
	sz[x]=1;
	f[x][0]=0;
	for(int i=head[x];i;i=e[i].nxt){
		int y=e[i].to;
		dp(y);
		sz[x]+=sz[y];
		for(int t=min(sz[x],m);t>=0;t--){//选课门数(容量),倒序循环来正确处理体积为 0 的物品 
			for(int j=0;j<=min(t,sz[y]);j++){
			    f[x][j]+=f[y][0];
				if(t-j>=0 && f[x][t-j]!=-1)
					f[x][t]=max(f[x][t],f[y][j]+f[x][t-j]); 
			}
		}
	}
	if(x!=0){
		for(int t=m;t>0;t--)f[x][t]=f[x][t-1]+w[x];//最后必须覆盖掉 ,自己占一份体积 
	}
}
int main(){
	scanf("%d%d",&n,&m);
	memset(f,-1,sizeof f);
	for(int i=1;i<=n;i++){
		int pre;
		scanf("%d%d",&pre,w+i);
		if(pre)link(pre,i);
		else link(0,i);
	}
	dp(0);
	printf("%d",f[0][m]);
	return 0;
}

Strategic game

这道题的意思是选择最少的点来覆盖一棵树,可以用最小点覆盖(也就是二分图最大匹配)或者树形dp来做,因为这里我们的专题是树形dp,所以我们现在就讲树形dp的做法。
我们做这道题的方法是用选择方程来做,因为你要做最小点覆盖,要么选这个点要么不选对吧。
于是dp的转移方程就是上述一方程

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
int n;
struct edge
{
    int next,to;
} e[4000];
int head[4000],tot,dp[4000][2],ind[4000];
void add(int x,int y)
{
    e[++tot].next=head[x];
    head[x]=tot;
    e[tot].to=y;
}
void dfs(int x)
{
    dp[x][1]=1;
    for(int i=head[x]; i; i=e[i].next)
    {
        int v=e[i].to;
        dfs(v);
        dp[x][0]+=dp[v][1];
        dp[x][1]+=min(dp[v][0],dp[v][1]);
    }
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        memset(head,0,sizeof(head));
        memset(ind,0,sizeof(ind));
        tot=0;
        for(int j=1; j<=n; j++)
        {
            int a,b;
            scanf("%d:(%d)",&a,&b);
            for(int i=1; i<=b; i++)
            {
                int c;
                scanf("%d",&c);
                ind[c]++;
                add(a,c);
            }
        }
        int rt;
        for(int i=0; i<=n; i++)
            if(!ind[i])
            {
                rt=i;
                break;
            }
        dfs(rt);
        printf("%d\n",min(dp[rt][1],dp[rt][0]));
    }
}
posted @ 2021-08-12 16:26  ¶凉笙  阅读(332)  评论(0编辑  收藏  举报