专题 线性DP-背包问题之多重背包

​

概念解释

什么是多重背包?可以结合完全背包理解。前面讲过,

完全背包,不同于最基本的0/1背包,对于每一个物体,可以选择任意件。即物体选择的数目是不做限制的。在这种情况下,结合0/1背包的概念所构成的模型,就是完全背包。

现在我们增加一个约束: 

物体选择的数目是不做限制的 - > 物体选择的数目是给定的

结合0-1背包的模型,现在概括出多重背包的特点:在多重背包的基础上,给出每一个物品最多选择的数量 cnt_{i} ,在此基础上求解题目。给出模板题:luogu.P1776 宝物筛选

规律提炼

那很容易想到朴素的算法,也就是我们在说完全背包时定义的状态:

定义状态:

f_{i},_{j},_{k} 为当前处理到第 i 个物品,还剩下 j 的空间,第 i 个物品选了 k 个的价值。

现在考虑转移。

状态转移:

顺次枚举 i , j , k ,分别从1...n,1...V,1...cnt_{i} ,并且考虑每个物品选或不选。这样可以得到状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-v_{i}\times k]+k\times p_{i}),其中0\leq k\leq cnt_{i} \cap k\times v_{i}\leq V。

算法分析

上面朴素的算法是很高的复杂度的,现在尝试作优化。由先前的经验不难知道,时间复杂度可以优化到 O(n^{2}) ,空间复杂度可以到 O(n) ,这是完全背包的优化,但是这里会有很大的不同。

我们介绍一个算法:二进制状态压缩。

概念:把物品i的件数ti分解成若干个件数的集合,集合里面的数字可 以组合成任意小于等于ti的件数,而且不会重复,之所以叫 二进制 分解,是因为这样分解可以用数字的二进制形式来解释。

下面有两个定理,我们列举出来。

定理1.   任何一个整数n均可以表示成:n=a_{0}+a_{1}\times 2+a_{2}\times 2^{2}+a_{3}\times 2^{3}+...+a_{k}\times 2^{k},a_{i}\in \left \{ 0,1 \right \},i_{i}\in\left [ 1,k \right ],i\in Z

定理2.   一个正整数 n 可以被分解成 1, 2, 4, … , 2^{k-1}, n-2^{k-1}+1( k 是满足 n-2^{k-1}+1>0 的最大整数)的形式,且1~n之内的所有整数均可以唯一表示成1, 2, 4, … , 2^{k-1}, n-2^{k-1}+1中某几个数的和的形式。

下面我们来证明。

证明1.

应用带余数除法,对n 用2\times p+r表示,并假设对于任意的m< n,都有上述定理成立,那么显然由归纳假设可知,p也一定是小于n的。所以 p也是可以表示成

p=b_{0}+b_{1}\times 2+b_{2}\times 2^{2}+b_{3}\times 2^{3}+...+b_{t}\times 2^{t},b_{i}\in \left \{ 0,1 \right \},i\in\left [ 1,t \right ],i\in Z 的。

将上面的表达式代入n也即2\times p+r中,展开并整理即得

n=r+b_{0}+b_{1}\times 2^{2}+b_{2}\times 2^{3}+b_{3}\times 2^{4}+...+b_{t}\times 2^{t+1},b_{i}\in \left \{ 0,1 \right \},i\in\left [ 1,t \right ],i\in Z

并令a_{0}=r,

       a_{1}=b_{0},

……a_{k+1}=b_{t}

比照对应项系数,

得证。

证明2. 

由定理1.可知,对于任意整数,其二进制分解是存在且唯一的。所以对于一个区间来说,都是可以被表示的,那么剩下这个序列的最后一项可以表示从最后一项到 n 的区间内也是会被组合得到的。这里只是一个简单的说明,因为道理比较容易理解。

对于上述数学原理的应用

既然可以将一个整数分解成唯一的一种由二进制数表示的方法,为何不尝试将多个同种物品分解成不同种的物品?这样就成功将多重背包转化为了0-1背包。具体思想是:

将同种的物品数利用二进制拆分,化为多个不同种的但是数量均为1的物品,这样原问题就等效成一个0-1背包的问题,这样就方便求解了。

这个二进制拆分的时间复杂度易于证明。为 O(\log _{2}T) 。总体时间复杂度为O((n+V)\times \sum (\log _{2}T)),其中T为被分解的值。

参考程序

#include<iostream>
using namespace std;
const int N=1e4+5,INF=0x3f3f3f3f;
int dp[N][N],idx;
struct node { int v,w,m; }a[N];
int main()
{
	int n,W;
	cin>>n>>W;
	for(int i=1;i<=n;i++)
	{
		int vi,wi,mi;
    	cin>>vi>>wi>>mi;
    	for(int j=1;j<=mi;j<<=1)
    	{
    		idx++;
    		a[idx].v=j*vi;
    		a[idx].w=j*wi;
    		mi-=j;
		}
		if(mi>0) a[++idx].v=mi*vi,a[idx].w=mi*wi; 
	}
	for(int i=1;i<=idx;i++)
		for(int j=0;j<=W;j++)
			if(a[i].w<=j) dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i].w]+a[i].v);
			else dp[i][j]=dp[i-1][j];
	cout<<dp[idx][W]<<endl;
	return 0;
}
//时间复杂度O(n^3),空间复杂度O(n^2),30pts,70(#4-#10)TLE;
//时空复杂度O(n^2), 
//时间复杂度O(n^2),100pts;

细节实现

单独给出二进制拆分的程序。

 

注意不能掉下最后一句。这是一个余项,表达式前面提到过,为 n-2^{k-1}+1 。这个余项是怎么得到的?其实就是前面的等比数列的和被n 减去的值,又由定理1、2易知其作用——为了表示等比数列最后一项为区间左端点至n 为区间右端点这一区间的值。

总结归纳

完全背包还是多重背包的特殊情况,即同种物品的选取是不做限制的,但是归根结底对于每个物品无非是选或不选,所以本质都是0-1背包。

​
posted @ 2025-07-23 15:29  枯骨崖烟  阅读(185)  评论(0)    收藏  举报