noip2006 金明的预算

题目链接:传送门

题目大意:略。。

题目思路:其实单就这道题来说,一个主件最多两个附件,且附件不再包含附件,所以很简单,但是如果主件的附件无限制,附件也可包含无限制的附件,应该怎么做?

     首先推荐一篇论文  徐持衡的《浅谈几类背包题》,里面有详细的讲解,这一类树形依赖背包问题最终都能优化到  n*c 复杂度,n为物品数,c为背包容量

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <stack>
#include <cctype>
#include <queue>
#include <string>
#include <vector>
#include<functional>
#include <set>
#include <map>
#include <climits>
#define lson root<<1,l,mid
#define rson root<<1|1,mid+1,r
#define fi first
#define se second
#define ping(x,y) ((x-y)*(x-y))
#define mst(x,y) memset(x,y,sizeof(x))
#define mcp(x,y) memcpy(x,y,sizeof(y))
using namespace std;
#define gamma 0.5772156649015328606065120
#define MOD 1000000007
#define inf 0x3f3f3f3f
#define N 1000005
#define maxn 32005
typedef pair<int,int> PII;
typedef long long LL;

int n,m,x;
vector<int>V[1005];
int dp[105][maxn];
int w[1005],v[1005];
void dfs(int u,int m){
    if(m<=0)return;
    for(int e:V[u]){
        for(int k=0;k<=m;++k)dp[e][k]=dp[u][k];
        dfs(e,m-w[e]);
        for(int k=w[e];k<=m;++k)dp[u][k]=max(dp[u][k],dp[e][k-w[e]]+v[e]);
    }
}
int main(){
    int i,j,group,x,y;
    while(scanf("%d%d",&m,&n)!=EOF){ 
        mst(dp,0);
        for(i=0;i<=n;++i)V[i].clear();
        for(i=1;i<=n;++i){
            scanf("%d%d%d",&w[i],&v[i],&x);
            v[i]*=w[i];
            V[x].push_back(i);
        }
        dfs(0,m);
        printf("%d\n",dp[0][m]);
    }
    return 0;
}

 

 

然后再从这道题谈谈我对树形依赖背包的理解,不管怎么想怎么做,这是背包问题。

也就是对于一个物品,存在拿与不拿的情况,而如果不拿父节点,那么子节点也就只有不拿的情况。

加上树形结构以后,我们依然要保持背包的性质不变,也就是维护容量V包含价值最优的情况。

因为N*C^2复杂度有太多优秀的博客介绍,这里就不再赘述。

讲下个人对于 N*C  复杂度解法的理解。首先 二维数组 dp[u][V] ,u代表当前处理的节点u, V代表当前处理的容量(V<=背包总容量),整个 dp[u][V]保存的是处理到节点u容量V时的最优价值(处理到u,代表u节点左边所有的节点都已处理完毕)以及u所有子节点容量为V的最优值。

我们在处理节点时,存在拿与不拿的情况,而想选子节点则必须拿。那么假设处理到了节点x,我们可以先强制拿节点 x,这样就可以很方便的处理x的子节点,递归下去,对于子节点也同样处理,这样递归到底层时,我们把能拿的子节点都拿完了,但是这不一定是最优解,所以回溯的时候,则需要对拿与不拿进行取舍,因为是从子节点回溯,并且我们递归的时候是强制拿父节点的,因此可以直接选择 拿不拿子节点,而不用管父节点的限制,这样回溯维护最优值。

 

vector<int>V[1005];
int dp[105][maxn];
int w[1005],v[1005];
void dfs(int u,int m){    ///u 父节点,m 当前可用容量(m<总容量)
    if(m<=0)return;   ///没有可用容量,背包装满了。。
    for(int e:V[u]){  /// e 子节点
        for(int k=0;k<=m-w[e];++k)dp[e][k]=dp[u][k];  ///    * 1 *
        dfs(e,m-w[e]);   ///强制拿e,方便处理e子节点     * 2 *
        for(int k=w[e];k<=m;++k)dp[u][k]=max(dp[u][k],dp[e][k-w[e]]+v[e]);
                         ///回溯更新 拿e后的最优值 与 不拿e ,保存两者最优值
    }
}

 

在分析 * 1 *与 * 2 *;

* 1 *:和学长讨论了下是否有必要加上这个赋值循环?当然需要。对于节点e来说,强制拿e是在处理完e之前所有节点的基础上进行的,也就是在之前节点最优值的基础上进行的。

     否则对于 e 来说,它与整个树的关系脱离,它最后得到的值与与父节点保存的值没有比较的意义。

* 2 *:m-w[e]就代表强制拿了e,因为背包中已经有部分体积被e 所占,e的价值只需回溯的时候加上进行更新即可。但是这里似乎没有体现出 强制拿 u?因为依赖背包题目一般给

   的是一片森林。我们为了方便处理,可以加上一个虚拟树根,重量价值都为0,这样调用的时候,直接dfs(虚拟树根,背包总容量)。

不过感觉* 1 *处还是没有解释好,希望能有大牛补充,有不对的地方也请指出,,,

 

posted @ 2016-07-29 09:42  Kurokey  阅读(178)  评论(0编辑  收藏  举报