zoukankan      html  css  js  c++  java
  • Luogu-P2015 二叉苹果树

    题目

    题目链接

    测试得分:  100

    主要算法 :  树型DP、零一背包

    题干:

       树上零一DP

    应试策略:

    1. 记忆化搜索
    2. 如果是空结点
    3. 如果是叶子结点直接返回苹果数
    4. 以上都不满足的话,DP状态转移
    5. 设置DP的转移,转移到左右儿子结点
    6. for(int i=1;i<cnt-1;i++) f[u][cnt]=max(f[u][cnt],Dp(edge[p[u].lc].to,i)+Dp(edge[p[u].rc].to,cnt-i-1)+edge[p[u].lc].dis+edge[p[u].rc].dis);
    7. 结果31分  

       错因分析:

        1.说白了,记忆化搜索还不是掌握的特别好

        2.其次状态设计不是特别好,在存储左右儿子时,应该该需要存储边的

       代码

    #include<stdio.h>
    #include<stdlib.h>
    #define LL long long 
    #define FORa(i,s,e) for(LL i=s;i<=e;i++)
    #define FORs(i,s,e) for(LL i=s;i>=e;i--)
    
    using namespace std;
    const LL N=1000;
    LL n,m,num_edge,sz[N+1],head[N+1],f[N+1][N+1];
    /*f[u][j]表示的是树根为u的子树中保留j条树枝的最多的苹果数 
    sz[u]表示的是树根为u的子树的大小 
    */
    struct Edge{
        LL next,to,dis;
    }edge[2*N+2];
    struct Mess{
        LL lc,rc;
    }p[N+1];
    inline void Add_edge(LL from,LL to,LL dis)
    {
        edge[++num_edge]=(Edge){head[from],to,dis},head[from]=num_edge;
    }
    inline LL max(LL fa,LL fb){return fa>fb?fa:fb;}
    inline LL min(LL fa,LL fb){return fa<fb?fa:fb;}
    
    void Dfs(LL u,LL fa)
    {
        sz[u]=1;
        for(LL i=head[u];i;i=edge[i].next)
        {
            LL v=edge[i].to;
            if(v!=fa)
            {
                Dfs(v,u);
                sz[u]+=sz[v];
                FORs(j,min(sz[u],m),0)//零一背包,对于每一种选择只能选一次 
                    FORa(k,0,min(sz[v],j-1))
                        f[u][j]=max(f[u][j],f[v][k]+f[u][j-k-1]+edge[i].dis);
                /*对于状态转移,以u为树根,保留j条树枝的最多的苹果数
                等于以u的儿子节点为树根,保留j-k-1条树枝的最多的苹果数加上以v为树根,保留k(k<=min(sz[v],j-1))条树枝的最多的苹果数*/
            }
        }
    }
    int main()
    {
        LL from,to,dis;
        scanf("%lld%lld",&n,&m);
        FORa(i,2,n)
        {
            scanf("%lld%lld%lld",&from,&to,&dis);
            Add_edge(from,to,dis),Add_edge(to,from,dis);
        }
        Dfs(1,0);
        printf("%lld",f[1][m]);
        return 0;
    }
    /*5 2
    1 3 1
    1 4 10
    2 3 20
    3 5 20*/

    优化策略:

      记忆化搜索,树型背包DP

      这道题有一个隐含的条件,当某条边被保留下来时,从根节点到这条边的路径上的所有边也都必须保留下来

      设f[u][i]表示uu的子树上保留ii条边,至多保留的苹果数目

      那么状态转移方程也就显而易见了

        FORs(j,min(sz[u],m),0)//零一背包,对于每一种选择只能选一次
          FORa(k,0,min(sz[v],j-1))
          f[u][j]=max(f[u][j],f[v][k]+f[u][j-k-1]+edge[i].dis);

      u表示当前节点,v是u的一个子节点,sz[u]表示uu的子树上的边数,m就是题目中要求的最多保留边数

      那么为什么是

      首先,为什么是f[u][i-j-1]而不是f[u][i-j]

      为前文提到了,保留一条边必须保留从根节点到这条边路径上的所有边,那么如果你想从u的子节点v的子树上留边的话,也要留下u,v之间的连边

      那么取值范围k为什么要小于等于i1而不是ii呢?

      同上,因为要保留u,v连边

      对了,别忘了i,j要倒序枚举因为这是01背包

      代码

    #include<stdio.h>
    #include<stdlib.h>
    #define LL long long 
    #define FORa(i,s,e) for(LL i=s;i<=e;i++)
    #define FORs(i,s,e) for(LL i=s;i>=e;i--)
    
    using namespace std;
    const LL N=1000;
    LL n,m,num_edge,sz[N+1],head[N+1],f[N+1][N+1];
    /*f[u][j]表示的是树根为u的子树中保留j条树枝的最多的苹果数 
    sz[u]表示的是树根为u的子树的大小 
    */
    struct Edge{
        LL next,to,dis;
    }edge[2*N+2];
    struct Mess{
        LL lc,rc;
    }p[N+1];
    inline void Add_edge(LL from,LL to,LL dis)
    {
        edge[++num_edge]=(Edge){head[from],to,dis},head[from]=num_edge;
    }
    inline LL max(LL fa,LL fb){return fa>fb?fa:fb;}
    inline LL min(LL fa,LL fb){return fa<fb?fa:fb;}
    
    void Dfs(LL u,LL fa)
    {
        sz[u]=1;
        for(LL i=head[u];i;i=edge[i].next)
        {
            LL v=edge[i].to;
            if(v!=fa)
            {
                Dfs(v,u);
                sz[u]+=sz[v];
                FORs(j,min(sz[u],m),0)//零一背包,对于每一种选择只能选一次 
                    FORa(k,0,min(sz[v],j-1))
                        f[u][j]=max(f[u][j],f[v][k]+f[u][j-k-1]+edge[i].dis);
                /*对于状态转移,以u为树根,保留j条树枝的最多的苹果数
                等于以u的儿子节点为树根,保留j-k-1条树枝的最多的苹果数加上以v为树根,保留k(k<=min(sz[v],j-1))条树枝的最多的苹果数*/
            }
        }
    }
    int main()
    {
        LL from,to,dis;
        scanf("%lld%lld",&n,&m);
        FORa(i,2,n)
        {
            scanf("%lld%lld%lld",&from,&to,&dis);
            Add_edge(from,to,dis),Add_edge(to,from,dis);
        }
        Dfs(1,0);
        printf("%lld",f[1][m]);
        return 0;
    }
    /*5 2
    1 3 1
    1 4 10
    2 3 20
    3 5 20*/

    总结:

      1.DP状态设计自己一定要铭记

      2.DP初始化与边界处理

  • 相关阅读:
    2.1 CDN Bypass
    4.1 SQL Injection
    ztosec/secscan-authcheck 安装部署教程
    浏览器的内部构造
    左右值的概念
    网络安全学习路线
    Python+Typora博客图片上传
    微信欢乐五子棋辅助
    [SUCTF 2019]EasySQL
    [护网杯 2018]easy_tornado (SSTI)
  • 原文地址:https://www.cnblogs.com/SeanOcean/p/11312952.html
Copyright © 2011-2022 走看看