标签:
ACM刷了一年多,背包学了无数次,学一次忘一次我也是醉了。
特此来纪念一下傻逼的我学习背包的结果。。。
顺便教教可爱又迷人的女朋友。
===========================尼玛,背了个包====================================
【包】:
首先我们要来说一下这个包,背的这个包不是普通的包,因为普通的包不会这么恶心的(生无可恋)。。。
这个包听说是个双肩包,不过我喜欢单肩包,这可能就是我记不住这个包的原因吧。。
包有两个参数,一个是可装的总体积 V;还有一个就是可以获得的总重量 W(一般我们也把这个重量表示为价值);
同时呢,我们往里塞的东西也是有相应的两个参数 v[ i ] , w[ i ] ;
两个参数对应的就是它占用的体积 和 他带来的价值;
好了,背景介绍完毕!
< 0 - 1 背了个包 >************************
0-1 背包就是背包的精神内涵了(可怕.jpg)。
【基本描述】: 给n个物品,每个物品有v[ i ],w[ i ];每个物品只有1个,问!这个包最大能装多少的价值;
【掰扯掰扯】:傻逼一点的人肯定会想找性价比高的放,当然是不行的喽。反例自举。
然后就诞生了0-1来背这个包;
我们设 Dp[ i ] 表示:当背包的容量为 i 的时候,可以装入的最大的价值,(Dp[ i ]表示获得的最大价值);
然后对于每一件物品(假设当前考虑的物品是第 j 件),我们枚举一下这件物品 放 还是 不放 到这个包里;
①:放
因为我当前最大的容量是 i ,所以我放这个物品进去就要给它空出来它这么大的容量v[ j ],
而且,空出来的这段容量所带来的价值是确定的,
就是w[ j ],所以此时,我背包的总的价值就是 Dp[ i ] = Dp[ i - v[ j ] ] + w[ j ];
(假设Dp[ i - v[ i ] ]是已知的);
②:不放
不放就是不放,就没有什么好说的了,此时的最大的价值就是 Dp[ i ] 它原来的值了;
③:你到底放还是不放啊!
当然是找两种方式中较大的一种喽。。
原因很简单嘛,加入此时 i = V, 那么此时就是最后的结果了,我当然是要大的那个喽。
其实更理论一点的说法是,动态规划 思维就是:通过子问题最优解得到全局的最优解,
所以每一步我们都要得到最优解。
然后我们的算法过程就很简单了:
for(int j = 0; j < n; j++) { for(int i = v; i >= v[j]; i--) { Dp[i] = max( Dp[i] , Dp[i-v[j]] + w[j] ); } }
代码中有一些需要解释的是,第一层枚举我当前的物品 j ,第二层是从最大的容量开始考虑的,
为什么呢,其实很简单,因为每个物品只能用一次,
i - v[ i ] 是比 i 容量小的状态,如果我们从小往大的枚举的话,那么在 i - v[ j ] 的状态的时候,
如果我们选择了放 j 进去,就意味着j 被用掉了,
并且改变了Dp[ i - v[ j ] ] 的值,我们后来的枚举就不能保证只使用一次 j 了。
倒着枚举可以避免这种状况的出现;
[ 注 ]:
使用前要记得把Dp数组清空为 0 ;
[ 问题变化 ]:
①:问你恰好装满的最大价值。
解决这种问题的时候只要把Dp数组初始化改变一下,DP[ 0 ] = 0; 其余的Dp全部赋为 [ 负无穷 ]。
[ 负无穷 ] 表示该种容量的时候不合法,这样在更新的时候,不合法的背包始终都会是负数,
而合法的背包容量就是正数。
最后判断一下Dp[ V ] 是否是负数就知道是不是合法的结果了。
< 完全 背了个包 >************************
完全背包就是0-1背包的简化版了。
【基本描述】:给你n个物品,每个物品还是那两个参数,但是每一个都可以使用无限多次;
【随便扯扯】:唯一的不同就是枚举的顺序,这次是从前往后的了。至于为什么不懂自己想。
for(int j = 0; j < n; j++) { for(int i = v[j]; i <= V; i++) { Dp[i] = max( Dp[i] , Dp[i-v[j]] + w[j] ); } }
其它的东西都是一样的了,包括初始化和问题变种;
< 多重 背了个包 >************************
多重背包就是完全背包和0-1背包的合体版了(就是这么可怕)。
【基本描述】:给你n个物品,每个物品还是那两个参数,但是每一个都可以使用的次数是给定的;
【随便扯扯】:唯一的不同就是枚举的顺序,这次不是从前往后的了,也不是从后往前的,
而是根据可用的数量,选择枚举的顺序。
这个模板使用了二进制优化;不懂以后慢慢理解;
为什么不写了呢,因为女朋友刚刚跟我生气来着,浪费了好长时间呢,所以不写了!
这告诉我们,哄好女朋友才是提高效率的唯一途径,没有的就算了。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; int Dp[60005]; void ZeroOne( int cost ,int weight ,int sum ) { for( int i = sum ; i>= cost ; i-- ) Dp[i] = max( Dp[i], Dp[i - cost] + weight ); } void Complete( int cost ,int weight,int sum ) { for( int i = cost ; i<= sum ; i++ ) Dp[i] = max( Dp[i], Dp[i-cost] + weight ); } void Multi( int cost ,int weight, int count, int sum ) { if( cost * count >= sum ) Complete( cost , weight, sum ); else { int i=1; while( i < count ) { ZeroOne( i*cost , i*weight, sum ); count -= i ; i<<=1 ; } ZeroOne( cost* count , weight * count , sum); } }
【进阶问题】:现在问题变了,物品还是原来的物品,现在我要问你能不能把在这个包装满,
我不考虑物品的价值,只问你能不能装满。你说!能不能装满!
其实都一样,就是个初始化的问题。。。
不过今天做到BC一道题,题目要求取 K 个装满 V,这个。。还要思考一下行不行
(其实就是下面要讲的这个 二维费用背包问题);
< 二维费用 背了个包 >************************
二维费用背包就是背包的升级版了。
【基本描述】:给你n个物品,每个物品三个参数,包括两个费用参数,我们可以简单的理解为 va[ j ] 和 vb[ j ];
【随便扯扯】:都说了是二维的了,此时的Dp当然就变成二维数组了,Dp[ U ][ V ] 表示这个包两种费用的最大承受值;
那么对于一个物品,还是那样子,放还是不放,方放几个。。。也就是上面的几种DP方式;
不同的是,对于状态转移的时候,要枚举两层了;
void ZeroOne(int cost_u,int cost_v, int weight, int u, int v) { for(int i= u; i>=cost_u; i--) for(int j=v; j>=cost_v; j--) Dp[i][j] = max( Dp[i][j], Dp[i-cost_u][j-cost_v]+weight); } void Complete(int cost_u,int cost_v, int weight, int u, int v) { for(int i= cost_u; i<=u; i++) for(int j=cost_v; j<=v; j++) Dp[i][j] = max( Dp[i][j], Dp[i-cost_u][j-cost_v]+weight); } void Multi(int cost_u,int cost_v, int weight,int count,int u,int v) { if(cost_u * count >=u && cost_v * count >=v) Complete(cost_u, cost_v, weight, u, v); else { int i=1; while( i < count ) { ZeroOne(cost_u*i, cost_v*i ,weight*i, u, v); count -= i; i<<=1; } ZeroOne(cost_u*count, cost_v*count, weight*count, u, v); } }
明天继续.。。。。。。。。挂机ing
标签:
原文地址:http://www.cnblogs.com/by-1075324834/p/5449956.html