题目链接:传送门
题目大意:略。。
题目思路:其实单就这道题来说,一个主件最多两个附件,且附件不再包含附件,所以很简单,但是如果主件的附件无限制,附件也可包含无限制的附件,应该怎么做?
首先推荐一篇论文 徐持衡的《浅谈几类背包题》,里面有详细的讲解,这一类树形依赖背包问题最终都能优化到 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 ",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 *处还是没有解释好,希望能有大牛补充,有不对的地方也请指出,,,