题解 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] = max\begin{cases} f[i - 1][j] \\ f[i - 1][j - v] + w & j \geq v\end{cases}\)

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

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

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

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

\(f[i][j] = max\begin{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\n",f[n][m]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/colazcy/p/11514778.html