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
  • 相关阅读:
    linux下udp编程
    gitlab和Django实现push自动更新
    gitlab和Django实现push自动更新
    gitlab和Django实现push自动更新
    python通过163邮箱发送邮件
    python通过163邮箱发送邮件
    爬虫的分类
    Webmagic之使用Pipeline保存结果
    Webmagci功能--获取链接
    Webmagic功能--抽取元素
  • 原文地址:https://www.cnblogs.com/Hoyoak/p/11825942.html
Copyright © 2011-2022 走看看