首先是自由评述:这几天的背包问题
0 1包:逆序。结果由上一组元素刷过去的结果得来的。
1 void ZeroOnePack(int val,int vol) 2 { 3 4 int i; 5 for(i=V;i>=vol;i--) 6 { 7 if(dp[i-vol]+val>dp[i]) 8 { 9 dp[i] = dp[i-vol]+val; 10 } 11 } 12 }
满 包:初始化的时候。这个有个好方法 -inf 其中const int inf = 1<<28-1;这样的。写在顶部
结果符合的得重新附值。 dp[0]=0常见的。
原理:明确的是这个值要足够小或者大。能导致过滤掉这个结果。
完全背包:顺序。注意这个复杂度只有N*V了。因为不是一个物品刷一层循环 而是一种物品刷一层循环。并不能用01包的原理去单纯解释。
1 void ZeroOnePack(int val,int vol) 2 { 3 4 int i; 5 for(i=vol;i<=V;i+) 6 { 7 if(dp[i-vol]+val>dp[i]) 8 { 9 dp[i] = dp[i-vol]+val; 10 } 11 } 12 }
多重背包:如果说是利用完全包的刷一层方法。并不好知道你的个数使用了多少来维护个数!。所以分完全包的情况vol*num>=V 也就是说在这个范围内你可以随意数量放置。(当然拉。必须是最优的。)而如果小于这个值 就用01包。 但是是用bin优化过的再进行01包。 这个时候是顺序还是逆序呢?是顺序的。理解可以用01包去里面。当初错误理解完全包的方法可以在这里得到好的解释。所以用一个变量k=1 来进行。最后还得进行一次num的01至于为什么 这个和bin 有关
1 void MulPack(int val,int vol,int num) 2 { 3 if(val*num>=aver) 4 { 5 ComPack(val,vol); 6 return; 7 } 8 9 int k=1; 10 while(k<num) 11 { 12 ZeroOnePack(k*val,k*vol); 13 14 num -= k; 15 k *= 2; 16 } 17 ZeroOnePack(num*val,num*vol); 18 }
混合背包:这种背包并不难,只要传入方式正确。以及注意满包问题。
1 int max(int a,int b) 2 { 3 return a>b?a:b; 4 } 5 void OneZeroPack(int val,int vol) 6 { 7 int j; 8 for(j=V;j>=vol;j--) 9 { 10 dp[j] = max(dp[j],dp[j-vol]+val); 11 } 12 } 13 void ComPack(int val,int vol) 14 { 15 int j; 16 for(j=vol;j<=V;j++) 17 { 18 dp[j] = max(dp[j],dp[j-vol]+val); 19 } 20 } 21 void MulPack(int val,int vol,int num) 22 { 23 if(val*num>=V) 24 { 25 ComPack(val,vol); 26 return; 27 } 28 int k = 1; 29 while(k<num) 30 { 31 OneZeroPack(val,vol); 32 num -= k; 33 k *= 2; 34 } 35 OneZeroPack(val,vol); 36 }
二维费用:说白了是添加一个数组。多一层循环。实际上经常和满包之类的在一起。这个时候注意初始化即可。另外,二维费用经常出现的是数目的限制。必须放入指定M个数目。这个时候往往就是满包问题了。由此可以拓展到最大n数量 最小n数量的问题只要最后查找一遍获取结果即可
另外要注意的地方。拿watch movie 这题来讲吧。http://acm.hdu.edu.cn/showproblem.php?pid=3496
这就是一个二维费用的问题 一维是时间 二维是数目。注意题目要求the shop just sell M piece of movies (not less or more then)。指定M个数目,那么满包问题。满包就是初始化的时候无穷小或者无穷大来过滤结果。但是问题就在于如何初始化。dp[time][num]根据实际意义。这是一个数目上的满包。即 dp[j][0] = 0 而dp[j][i!=0] = -INF.这是容易思考的根据实际意义首先确定下来的就是状态描述是 在体积有j装了M 的数目下的最大价值.所以就第0个物品来讲。(初始化就是第0个物品)只有数目0才是有意义的。
1 for(j=0;j<=L;j++) 2 { 3 for(k=0;k<=M;k++) 4 { 5 if(k==0){dp[j][k]=0;} 6 else{dp[j][k]=-inf;} 7 } 8 }
分组背包(一):首先有组的概念。对于这个分组背包是一个组里只能取一个物品或者取0个物品。并且你必须知道的是。用二维来讲。划分的状态就是dp[i][j]前i组在体积为j下所能获得的最大价值。那么简单。就是在这个组里查找一个元素能使得放入后是最大的。那么必定多一个循环k。那么一维
//有N组 //针对不同的V //C[i][k],W[i][k]; for(i=1;i<=N;i++) { for(j=V;j>=0;j--) { //体积可是会变的 在这个组里所以>=0 for(k=1;k<=num[i];k++) {//第一个组的个数循环没问题。用来访问的 if(C[i][k]<=j) { dp[j] = max(dp[j],dp[j-C[i][k]]+W[i][k]); } //一开始是放入一个dp[j]代表的含义是上一层 之后就是含义就是比较大小了。 } } }
分组背包(二):仔细观察”分组背包的一维“ 你就能发现。其代码和01背包的如此相似。那我们把最内层的循环当做查找一个最优的解放入当前j(体积)的背包中。那么可以抽象出来 一个组的物品。其实可以代替上一个最优的物品。并且一个组的物品只能放一个。这便和01背包无异。只是你多了一层循环来查找那个最优的物品。然后放上这个最优的物品。而得到最好的解。其实这个最优的物品已经给取名叫做”泛化物品“。我想这是一个很好的解决放<=1个元素的物品的解决问题的思路。(如果没有组 你还不会吗?)。
分组背包(三):根据上述的认为一个组为一个最优物品。那么根据混合背包。我们也有混合分组背包问题。即有不同规则的组别。但是我们先解决分组背包的变式。一个经典的>=1的情况。对于>=1也就是说一定会在这个组别里取一个。对于这种问题。假如是在普通的背包问题(没有组别的)上你会如何解决?那么就是要避免不取的状态。也就是设定不取的状态是非法的(INF)也就是刷上这一层是INF的。这个和满包不同。满包的话。如果数目为0之类的初始化是可行的。其实是一个原理。其实这里的0组刷成0更符合常理。非法和合法状态 也就是说每次i++的时候我们都要在当前层刷一遍INF。鉴于分组背包数据量较多。我们常用的手段是边输入的同时边处理数据。这里就一个状态包就算了。这里的刷层是这样的。对于不同的体积先让第一个物品刷一层。因为物品只有一个。所以逆刷。然后让第二个物品接着刷一层。(其实就是组内01包的问题) 也就是第二层是物品 第三层是体积。这个和分组背包相反。因为分组背包。只能装一个或者不装。假如像上面那种刷法会导致物品放上2个或者3个。分组背包是在一个体积上寻找最优的一个。而非叠加。
伪代码:
1 dp[i][j] 2 //代表着处理了前i组在体积为j下所能获得的最大价值。 3 memset(dp,0,sizeof(dp))//没什么大不了的时候就0吧。因为全是合法的 4 for(i:1~N) 5 for(j:V~0) //所以尾巴还要判断要>=j 6 for(k:1~num[i]) 7 if(vol[k]>=j) 8 if(dp[i-1][j-vol[i]]+val[i]>dp[i][j]) 9 //从上一组的状态取下来的嘛。且dp[i][j]保留了上个物品的决策。 10 dp[i][j] = this. 11 12 13 memset(dp,INF,sizeof(dp)) 14 dp[0][j] = 0; 15 for(i:1~N) 16 for(k:1~num[i]) 17 for(j:V~0) 18 //以上都分析过了。 19 if(vol[k]>=j) 20 dp[i][j] = max(dp[i][j],dp[i][j-vol[k]]+val[k],dp[i-1][j-vol[k]]+val[k]); 21 22 // 不取,在这组物品上继续取,在这组上重新开始取