动态规划初步——背包问题

动态规划认知


​ 在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。

01 背包


问题

有n件物品和一个容量为m的背包,放入第i件物品所需要的空间为wi,第i件物品的价值为vi,问背包可以放入物品的最大价值为多少?

  1. 此问题不可用贪心算法,贪心法得到的往往不是最优解。
  2. 直接暴力穷举n件物品,时间复杂度是O(2^n),效率过低,且存在大量重复运算

算法

​ 综上我们采用两重循环的方式来解决此问题,避免了复杂的递归,并且采用数组存储数据,使得计算次数大大减少,复杂度由O(2^n)优化到了O(VW)*。

for(int i=1; i<=n; i++){        //物品 
    for(int j=V; j>=0; j--){    //容量 
        if(j >= w[i])   //放得下物品,使用状态转移方程
            dp[i][j] = max(dp[i-1][j-w[i]]+v[i], dp[i-1][j]);
        else            //放不下物品,直接跳过,沿承 i-1 个物品的价值
            dp[i][j] = dp[i-1][j];           
    }
}

优化

空间优化

​ 实际上,二维数组的空间复杂度过高,有时候也不现实,所以有时采用状态压缩,将二维数组压缩至一维。

压缩原理:实质上,i本身无实质性意义,在第 i+1 次循环尚未开始之前,第 i 行已经保存了当前的数据,且 0 到 i-1 行就没有使用价值了,故可以将二维数组压缩为一维数组,从后向前遍历,状态转移方程中的 [i-1] 就无存在价值了。

​ 对于外层循环中的每一个 i 值,其实都是不需要记录的,在第 i-1 次循环已结束且第 i 次循环未开始,dp数组还未更新时,dp[j]还记录着前 i-1 个物品在容量为 j 时的最大价值,这样就相当于还记录着 dp[i-1][j]dp[i-1][j-vol[i]]+val[i]

状态转移方程修改为 dp[j] = max(dp[j], dp[j - w[i]] + v[i])

恰好装满

如果要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1..V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。

如果没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0..V]全部设为0。 ——《背包九讲》

​ 可以将其理解为不能把背包填满的解都是非法(价值小于零)的,从而在计算中排除这些解。或者说当前的合法解一定是从之前的合法状态推得的。

​ 这个技巧可以推广到其他背包问题,不仅仅限于01背包。

常数优化

​ 经过图表可知,并不需要将表中的数据全部算出,只需要算一部分即可。因为最后所求结果为dp[n][v],其在 n-1 行所需的最小的 jv-w[n-1],同理,对于第 i 行, j 最小循环至 v- sum{w[n...i-1]} ,综上所述我们可以优化第二层的循环次数,代码如下:

   //理论上的第二层循环
   // bound=max{V-sum{w[i..n]},w[i]};
   // for(int j=V;j>=bound;j--)
for(i = 1; i<=n; i++){//实际代码
    int sum = 0;
    for(int k = i ; k <= n ; ++k){
        sum += w[k];
    lower = max(sum, w[i]);
    for(j = V ; j >= lower; j--){
        dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
    }
}

​ 因为若是 V-sum{w[i..n]} < w[i] 的话,则无法取到第 **i **个物品了,所以就把下限定为 w[i],进一步减少第二层循环次数,这种方法在V很大的情况下会节省时间。

封装

//kuangbin 和 《背包九讲》 中都给出了这个代码
//此处仅仅将第二层循环和数组封装了,相当于对一个物品进行处理,但是未加入常数优化
//但是在多重背包里会用到
void ZeroOnePack(int cost,int value){
     for(int j = nValue ; j >= cost ; j−−)
     dp[i] = max(dp[i] , dp[i−cost] + value);
 }

完全背包


问题

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

思考方向还是先从二维数组下手,得到转移方程,再向一维数组优化


算法

此处采用和01背包相似的思考方式,01背包每个物品都拿一次,所以对于同一个 i 下的 j 都应该是独立的,即每个 j 中至多包含一个当前物品,所以由后向前遍历,但是01背包中,每个 i 不止用一次,可能用多次,所以偏大的 j 中的情况就不应从i-1 那一行转移,而应该是从同一行中转移,这样才能保证可以出现多个 i

for(int i = 1; i <= n; i++)         //正序
    for(int j = 0; j <= V; j++){    //还是正序
        if(j >= w[i]){          //放得下
            dp[i][j] = max{dp[i-1][j] , dp[i][j-w[i]] + v[i];
        } else{                //放不下
            dp[i][j] = dp[i-1][j];         
        }
    }

思维陷阱

背包问题其实有一个隐藏的条件,就是包的体积和物品的质量其实都是整数,虽说是无限多的物品,但是实质上还是枚举出来了,在 i 行的遍历一定能枚举所有含 i 情况

完全背包每次做出选择是取决于当前这一步的,而01背包每次做出选择是取决于上一步的。主要就是因为当前这一步一个物品放过以后,表格逐渐向右填写,随着可放空间的增加,可以判断这一步是否还可以再放一个当前的物品。


优化

空间优化

和01背包一样,二维数组太过浪费空间,可以采用一维滚动数组的方式来减少空间开销。

转移方程可化为 dp[j] = max{ dp[j] , dp[j-w[i]] + v[i] }

常数优化

值得一提的是,上面的伪代码中两层 for 循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。

封装

//依旧是没有常数优化
//完全背包,代价为 cost, 获得的价值为 value,也是对一个物品的处理
 void CompletePack(int cost,int value){
     for(int i = cost;i <= nValue;i++)
        dp[i]=max(dp[i] , dp[i−cost] + value);
 }

多重背包


问题

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。


算法

我们在此考虑将多重背包转化为01背包和完全背包。

  1. 正如在完全背包中探讨的一样,所谓“无限”只是相对无限,即背包里全部放这个东西也无法全部将其放下时,就可以认为是无限了。
  2. 根据此方法,剩下的所谓有限的,就可以拆分成多个物品,根据01背包来计算。但是如果拆分成 n[i] ** 个效率就太低了,所以采用二进制优化这个问题。 使这些系数分别为1 , 2, 4 , ... , 2^(k-1) , n[i]-2^k+1 ,且k是满足n[i]-2^k+1>0的最大整数。 这是因为这些数之和为n[i]** , 这样就将 n[i]个物品优化到了 log( n[i] ) 个,提高了效率。
  3. 此问题可以用单调队列优化至 O(VM),即和前两种问题一样,但是太过复杂,日后补充。

void MultiplePack(int cost,int value,int amount){  //花费, 价值 , 数量
 if(cost*amount>=V)             //相对“无限”
     CompletePack(cost,value);      //完全背包
 else{
    int k=1;
    while(k < amount){
        ZeroOnePack(k*cost,k*value);    //系数递增,1,2,4,8,... 
        amount−=k;
        k<<=1;          //k左移一位,即k*=2
    }
    ZeroOnePack(amount*cost,amount*value);//这个不要忘记了,这是最后一个系数,即n[i]减去之前所有系数的结果
 }
}

(待思考)一种实现较为简单的O(V N) 复杂度解多重背包问题的算法。基本思想是这样的:设F[i; j] 表示“用了前i 种物品填满容量为j 的背包后,最多还剩下几个第i 种物品可用”,如果F[i, j] = -1 则说明这种状态不可行,若可行应满足0 ≤ F[i,j] ≤ Mi。

优化多重背包算法

混合三种背包


问题

有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢


算法

for i=1..N
    if 第i件物品属于01背包
        ZeroOnePack(c[i],w[i])
    else if 第i件物品属于完全背包
        CompletePack(c[i],w[i])
    else if 第i件物品属于多重背包
        MultiplePack(c[i],w[i],n[i])

评价

真的就这么简单粗暴,不过也体现了模块化之后的便利之处。(kuangbin模板中甚至都没有提到这个问题)


二维费用的背包问题


问题

对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值(两种背包容量)分别为A和B。物品的价值为v[i]。


算法

费用增加一维,其实只需将状态加一维即可。设 f[i][v][u] 表示前i件物品付出两种代价分别为 vu 时可获得的最大值。状态转移方程

f[i][w][u] = max{ f[i-1][w][u] ,f[i-1][w-a[i]][u-b[i]] +w[i]}

===TO BE CONTINUE==

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 树形动态规划,顾名思义就是树+DP,先分别回顾一下基本内容吧:动态规划:问题可以分解成若干相互联系的阶段,在每一个...
    Mr_chong阅读 1,433评论 0 2
  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,579评论 0 89
  • 动态规划(Dynamic Programming) 本文包括: 动态规划定义 状态转移方程 动态规划算法步骤 最长...
    廖少少阅读 3,235评论 0 18
  • 红燕收集 柚子---水果,味甘性寒,含有很丰富的有用物质,药用价值也不错。可以健胃化食、以及治疗咳嗽等症状。 ...
    红燕_f560阅读 347评论 0 2
  • 《让未来现在就来》这本书的作者是彭小六,彭小六何许人也?相信在简书混过的人都知道他,因为在简书上他最牛逼,外号“简...
    冷冷123456阅读 347评论 0 5