zoukankan      html  css  js  c++  java
  • 题解 P1064 【金明的预算方案】

    题目链接

    Solution [NOIP提高组2006]金明的预算方案

    题目大意:给定一系列物品,每个物品有一个价格(v)和权值(w),并且物品间存在依赖关系.求一种可行方案,使得满足依赖关系并且(sum v leq m)的情况下(sum w)尽量大

    树形背包,树转序列


    分析:这题很明显告诉你它的依赖关系是一颗树,比如如果一个物品(v)依赖物品(u),我们连一条边(u,v).为了方便,我们加入一个虚拟点(0),一个点(u)如果是主件我们就连((0,u))

    那么这样我们最后会得到一颗树,这个树有哪些特点呢?

    • 一个物品所依赖的所有物品都是他的父节点
    • 如果一个物品不选,对应到树上,他所有的子节点都不能选

    好了,树构建出来了.我们来看如何解题.首先我们可以用树形dp来求解,但是这样做比较复杂,而且跑的不是很快.那么可不可以用我们比较熟悉的(01)背包来求解呢?

    普通(01)背包的状态以及转移方程?

    (f[i][j] = maxegin{cases} f[i - 1][j] \ f[i - 1][j - v] + w & j geq vend{cases})

    这个相信大家十分熟悉,再看这题?树形(dp)不好做,我们可以把它变成线性dp.把一棵树变成一个序列我们可以怎么办?(dfs)序啊

    我们对这颗树求一个后序遍历,这样一个节点的儿子以及左边兄弟在序列中都在它的前部.然后呢?每一步我们都有两种决策.

    • 选这个物品,那么状态(i)可以直接由状态(i - 1)转移而来
    • 不选这个物品,那么是不是这个点的儿子都不可以选,那么状态(i)就只能由它的左兄弟转移而来

    整理一下,如果用(pre[u])表示编号为(u)的左兄弟的编号的话:

    (f[i][j] = maxegin{cases}f[pre[i]][j]\f[i - 1][j - v] + w & j geq v end{cases})

    分析一下时间复杂度?求后序遍历以及(pre)我们都可以通过一次(dfs)(O(n))的时间内求出来

    • 状态总数:(nm)
    • 转移代价:(O(1))
      那么总复杂度?(O(n + nm))所以总复杂度为(O(nm))(01)背包相当,而且这种算法还可以处理附件有附件等(也就是树的高度不只为(2))的情况

    附上丑陋的代码:

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int maxn = 64;
    const int maxm = maxn;
    const int maxv = 33333;
    struct Node{//表示物品
        int v,w;
        Node() = default;
        Node(int a,int b):v(a),w(b){}
    }d[maxn],t[maxn];
    struct Edge{
        int from,to;
        Edge() = default;
        Edge(int a,int b):from(a),to(b){}
    }Edges[maxm];
    int head[maxn],nxt[maxm],root[maxn],rp;
    inline void addedge(int from,int to){
        static int tot;
        Edges[++tot] = Edge(from,to);
        nxt[tot] = head[from];
        head[from] = tot;
    }
    int pre[maxn],f[maxn][maxv],p = 0;
    void dfs(int u){
        int tp = p;//由于后序遍历的性质,一个点的左兄弟显然是进入这个点时序列中的最后一个点
        for(int i = head[u];i;i = nxt[i]){
            Edge &e = Edges[i];
            dfs(e.to);
        }
        d[++p] = t[u];//后序遍历
        pre[p] = tp;//求左兄弟,注意,pre[t]表示序列中编号为t的节点的左兄弟的编号
    }
    int n,m;
    int main(){
    #ifdef LOCAL
        freopen("fafa.in","r",stdin);
    #endif
        scanf("%d %d",&m,&n);
        for(int i = 1;i <= n;i++){//建图
            int v,w,faz;
            scanf("%d %d %d",&v,&w,&faz);
            t[i] = Node(v,v * w);//先预处理它的权值
            addedge(faz,i);//有个技巧,如果一个点是主件,我们就认为它依赖于虚拟点0
        }
        dfs(0);
        for(int i = 1;i <= n;i++)//dp求解
            for(int j = 0;j <= m;j++)
                if(j >= d[i].v)f[i][j] = max(f[pre[i]][j],f[i - 1][j - d[i].v] + d[i].w);
                else f[i][j] = f[pre[i]][j];//转移 
        printf("%d
    ",f[n][m]);
        return 0;
    }
    
    
  • 相关阅读:
    HDU 2081 手机短号
    HDU 2053 Switch Game
    HDU 2040 亲和数
    HDU 2070 Fibbonacci Number
    redis集群安装2
    redis集群1
    批量更新sql
    centos 6升级 GCC 到4.8
    排序4 -- 插入排序
    排序3--选择排序
  • 原文地址:https://www.cnblogs.com/colazcy/p/11514778.html
Copyright © 2011-2022 走看看