zoukankan      html  css  js  c++  java
  • 树形DP

    考前不写博客就容易颓废QwQ,既然DP比较差就重新总结总结DP,说不定就总结到了...

    这一篇总结一下树形DP。

    树形DP的转移:儿子到父亲

    树具有天然的最有子结构,最有子结构即为儿子。

    树形DP的状态:一般设dp[u]表示以u为根的子树的最优子结构。

    树形DP可以结合树上的数据结构,同时巧妙运用DFS序、BFS序可以优化解法QwQ。

    问题1:树上最大独立集

    最大独立集定义:在树上取尽可能多的点,使得所选的点任意两个点都没有边相连。

    状态显然有dp[u][0/1],表示以u为根节点的子树选或者没选u的最大独立集,并且可以直接从子树转移来。

    dp[u][1]+=dp[e][0];(e是u的儿子)

    dp[u][0]+=max(dp[e][0],dp[e][1]);(e是u的儿子)

    附一个能过编译随手打的不知道对不对的代码

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    
    using namespace std;
    
    struct node
    {
        int ed,nxt;
    };
    node edge[2333<<1];
    int n,first[2333],cnt;
    int dp[2333][2];
    
    inline void add_edge(int s,int e)
    {
        ++cnt;
        edge[cnt].ed=e;
        edge[cnt].nxt=first[s];
        first[s]=cnt;
        return;
    }
    
    inline void dfs(int now,int fa)
    {
        dp[now][1]=1;
        for(register int i=first[now];i;i=edge[i].nxt)
        {
            int e=edge[i].ed;
            if(e!=fa)
            {
                dfs(e,now);
                dp[now][0]+=max(dp[e][1],dp[e][0]);
                dp[now][1]+=dp[e][0];
            }
        }
        return; 
    }
    
    int main()
    {
        scanf("%d",&n);
        for(register int i=1;i<=n-1;++i)
        {
            int s,e;
            scanf("%d%d",&s,&e);
            add_edge(s,e);
            add_edge(e,s);
        }
        dfs(1,0);
        printf("%d
    ",max(dp[1][0],dp[1][1]));
        return 0;
    }
    树上最大独立集

    扩展:最大权独立集,最大独立集可以视为权值为1,最大权独立集改改就好。

    问题2:树的直径

    树的直径的定义:树上距离最远的两个点的距离。

    首先树的直径可以由两遍BFS求出,先从任意一个点出发bfs找到距离这个点最远的那个点,这个点一定是树的直径的

    其中一个端点,然后再从那个点出发bfs一遍找到的另一个点就是树的直径的另外一个端点。

    考虑DP的做法(显然复杂度更低QwQ)设dp[u]为节点u到所有子树里面的最远距离,然后以经过u的直径就是它的儿子的

    dp的最大值+次大值,取max更新答案即可。

    inline void dfs(int now,int fa)
    {
        for(register int i=first[now];i;i=edge[i].nxt)
        {
            int e=edge[i].ed;
            if(e!=fa)
            {
                dfs(e,now);
                ans=max(ans,dp[e]+dp[u]+edge[i].len);
                dp[u]=max(dp[u],dp[e]+edge[i].len);
            }
        }
    } 
    树的直径DP

    问题3:树的最小点覆盖集

    最小点覆盖集:选取最少的点覆盖所有的边

    设dp[u][0/1]表示以u为根节点是否选u的子树的最小点覆盖集。

    因为要覆盖所有的边所以能轻松得到如果不选这个点的转移dp[u][0]+=dp[e][1];

    如果要选择这个点,dp[u][1]+=min(dp[e][0],dp[e][1]);

    inline void dfs(int now,int fa)
    {
        dp[now][1]=1;
        for(register int i=first[now];i;i=edge[i].nxt)
        {
            int e=edge[i].ed;
            if(e!=fa)
            {
                dfs(e,now);
                dp[now][0]+=dp[e][1];
                dp[now][1]+=min(dp[e][0],dp[e][1]);
            }
        }
        return; 
    }
    树的最小点覆盖集

    问题4:树的重心

    树的重心的定义:找到一个点,其所有的子树中最大的子树节点数最少,这个点叫做树的重心也叫树的质心。

    从定义直接出发即可,设dp[u]表示那个点的最大子树的节点数,取所有节点的min即可。注意必须以u为树根。

    inline void dfs(int now,int fa)
    {
        siz[now]=1;
        for(register int i=first[now];i;i=edge[i].nxt)
        {
            int e=edge[i].ed;
            if(e!=fa)
            {
                dfs(e,now);
                siz[now]+=siz[e];
                dp[now]=max(dp[now],siz[e]);
            }
        }
        dp[now]=max(dp[now],n-dp[now]);
        if(dp[now]<dp[ans]) ans=now;
        return;
    }
    树的重心

    问题5:树上背包(简化版)

    给出n个点的有根树,每个节点都是一个价值为val[i]的物品,要就如果选择物品i必须选择物品i的父亲节点,求

    至多选择m个物品的最大价值。

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #define maxn 310
    
    using namespace std;
    
    struct node
    {
        int ed,nxt;
    };
    node edge[maxn<<1];
    int n,m,first[maxn],cnt;
    int num[maxn],siz[maxn],dp[maxn][maxn];
    
    inline void add_edge(int s,int e)
    {
        ++cnt;
        edge[cnt].ed=e;
        edge[cnt].nxt=first[s];
        first[s]=cnt;
        return;
    }
    
    inline void dfs(int now,int fa)
    {
        siz[now]=1;
        dp[now][0]=0;
        for(register int i=first[now];i;i=edge[i].nxt)
        {
            int e=edge[i].ed;
            if(e!=fa)
            {
                dfs(e,now);
                siz[now]+=siz[e];
                for(register int j=m;j>=0;--j)
                    for(register int k=min(j,siz[e]);k>=0;--k)
                        dp[now][j]=max(dp[now][j],dp[now][j-k]+dp[e][k]);
            }
        }
        if(now!=0)
           for(register int i=m;i>0;--i)
                dp[now][i]=dp[now][i-1]+num[now];
        return;
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(register int i=1;i<=n;++i)
        {
            int e;
            scanf("%d%d",&e,&num[i]);
            add_edge(i,e);
            add_edge(e,i);
        }
        dfs(0,-1);
        printf("%d
    ",dp[0][m]);
        return 0;
    }
    树上背包

    问题6:一道入门的树形DP题——二叉苹果树

    Description

    有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)

    这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。

    我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树

    2   5
      / 
      3   4
        /
        1
    

    现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。

    给定需要保留的树枝数量,求出最多能留住多少苹果

    题解:很显然,这是最有值问题,而且在树上,我们考虑树形DP,我们可以设dp[u][i]表示以u为根节点的子树

    中保留i条树枝的最有值,我们发现转移就是一个01背包。

    转移方程 dp[u][i]=max(dp[u][i],dp[u][i-j-1]+dp[e][j]+edge[i].len);

    最后dp[1][m]即为答案。

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #define maxn 110
    
    using namespace std;
    
    struct node
    {
        int ed,len,nxt;
    };
    node edge[maxn<<1];
    int n,m,cnt,first[maxn],dp[maxn][maxn],siz[maxn];
    
    inline void add_edge(int s,int e,int d)
    {
        ++cnt;
        edge[cnt].ed=e;
        edge[cnt].len=d;
        edge[cnt].nxt=first[s];
        first[s]=cnt;
        return;
    }
    
    inline void dfs(int now,int fa)
    {
        for(register int i=first[now];i;i=edge[i].nxt)
        {
            int e=edge[i].ed;
            if(e!=fa)
            {
                dfs(e,now);
                siz[now]=siz[now]+siz[e]+1;
                for(register int j=min(siz[now],m);j>=0;--j)
                    for(register int k=min(j-1,siz[e]);k>=0;--k)
                        dp[now][j]=max(dp[now][j],dp[now][j-k-1]+dp[e][k]+edge[i].len);
            }
        }
        return;
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(register int i=1;i<=n-1;++i)
        {
            int s,e,d;
            scanf("%d%d%d",&s,&e,&d);
            add_edge(s,e,d);
            add_edge(e,s,d);
        }
        dfs(1,0);
        printf("%d
    ",dp[1][m]);
        return 0;
    }
    洛谷P2015
  • 相关阅读:
    Oracle常用命令大全(很有用,做笔记)
    表格驱动编程在代码中的应用
    mac 利用svn下载远程代码出现Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo.
    FAILURE: Build failed with an exception.
    There is an internal error in the React performance measurement code.Did not expect componentDidMount timer to start while render timer is still in progress for another instance
    react native TypeError network request failed
    Android向系统相册中插入图片,相册中会出现两张 一样的图片(只是图片大小不一致)
    react-native Unrecognized font family ‘Lonicons’;
    react-native SyntaxError xxxxx/xx.js:Unexpected token (23:24)
    Application MyTest has not been registered. This is either due to a require() error during initialization or failure to call AppRegistry.registerComponent.
  • 原文地址:https://www.cnblogs.com/Hoyoak/p/11825942.html
Copyright © 2011-2022 走看看