背包问题总结

01背包

题目

有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i],求将哪些物品装入背包可使价值总和最大。

基本思路

主要特征:每个物品只有一件,只有放与不放两种状态,设dp[i][j]表示重量限制为j时在前i个物品中能得到的最大价值

\[dp[i][j] = max(dp[i-1][j],dp[i-1][j -w[i]] + v[i]) \]

因为每个物品只有两种选择则第i个不选,最大价值为dp[i-1][j],第i个要选时就要为第i个物品腾出w[i]的空间,再加上v[i];

可以发现前i个的状态都是由前i-1个推来,则可以用滚动数组滚动一维

一维情况第二层倒序枚举原因:因为是一维,正序枚举会把第i层更新的值覆盖上去,而之后又傻乎乎把被覆盖的值当做第i-1的值使用(其实也就是第i个物品可能被多次选用,完全背包将要用到)

inline void _01beibao()
{
	for(rint i=1;i<=n;i++)
		for(rint j=m;j>=wi[i];j--)
			dp[j]=max(dp[j],dp[j-wi[i]]+vi[i]);
}

求01背包第k大

inline void _01kth()
{
	for(rint i=1;i<=n;i++)
	{
        for(rint j=m;j>=wi[i];j--)
        {
            int c1=1,c2=1,cnt=0;
            while(cnt<=k)
            {
                if(dp[j][c1]>dp[j-wi[i]][c2]+vi[i]) gg[++cnt]=dp[j][c1++];
                else gg[++cnt]=dp[j-wi[i]][c2++]+vi[i];
            }
            for(rint cc=1;cc<=k;cc++) dp[j][cc]=gg[cc];
              //这里dp[j][k],表示第i层价值为j时的前k大值,显然dp[i][v][1..K]这K个数是由大到小排列的,所以我们把它认为是一个有序队列。
              //然后原方程就可以解释为:dp[i][v]这个有序队列是由dp[i-1][v]和dp[i-1][v-c[i]]+w[i]这两个有序队列合并得到的。
			}
    }
}

完全背包

题目

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

基本思路

基本特点:每个物品有无数件,状态是取0-V/w[i]件。dp[i][j]定义同上,有

\[dp[i][j] = max(dp[i][j],dp[i-1][j-k \times w[i]] + k \times v[i]) \]

由于可以多次选取,正序枚举可以利用之前被w[i]更新的dp值(本质就是多次选择),也因此可滚动一维。

inline void wanquanbeibao()
{
	for(rint i=1;i<=n;i++)
		for(rint j=wi[i];j<=m;j++)
		dp[j]=max(dp[j],dp[j-wi[i]]+vi[i]);
}

多重背包

题目

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

基本思路

是完全背包在个数上限制,多一层个数枚举,有

\[dp[i][j] = max(dp[i - 1][j - k \times w[i]] + k \times v[i]) (0 <= k <= num[i]) \]

由于都是自上一层状态转移也可滚动一维

inline void duochongbeibao()
{
	for(rint i=1;i<=n;i++)//种类 
		for(rint j=m;j>=wi[i];--j)
			for(rint k=1;k<=num[i];++k)
            {
            	if(j-k*wi[i]>=0) dp[j]=max(dp[j],dp[j-k*wi[i]]+k*vi[i]);
                else break;
            }  
}

01背包的思想的二进制优化

把p[i]件物品化为以2的幂次为单位的几组,这几组数字可以完全表示[1,p[i]]的所有整数(不能理解重学二进制)复杂度从num降至log(num)

for (rint i = 1; i <= n; i++) {
    int num = min(p[i], V / w[i]);
    for (rint k = 1; num > 0; k <<= 1) {
        if (k > num) k = num;
        num -= k;
        for (int j = V; j >= w[i] * k; j--)
            f[j] = max(f[j], f[j - w[i] * k] + v[i] * k);
    }
}

单调队列优化

待更...

混合背包

题目

前三种背包的三种不同性质的物品组合起来

inline void hunhebeibao()
{
	for (rint i=1;i<=n;i++)
	{
    	if (num[i]==0)//完全 
    	for(rint j=wi[i];j<=m;++j)
        	dp[j]=max(dp[j],dp[j-wi[i]]+vi[i]);
        else
          for(rint j=m;j>=wi[i];--j)
              for(rint k=1;k<=num[i];++k)
              {
                  if(j-k*wi[i]>=0) dp[j]=max(dp[j],dp[j-k*wi[i]]+k*vi[i]);
                  else break;
              }     
	} 
}

二维费用背包

题目

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

基本思路

只要再加一维就可以了,当每件物品只可以取一次时变量j和k采用逆序的循环,当物品有如完全背包问题时采用顺序的循环。当物品有如多重背包问题时拆分物品。公式

\[dp[j][k] = max(dp[j][k],dp[j - w[i]][k - g[i]] + v[i]) \]

inline void erweibeibao()
{
	for (rint i=1;i<=n;++i)
		for(rint j=v1;j>=w1[i];--j)
			for(rint k=v2;k>=w2[i];--k)
            	dp[j][k]=max(dp[j][k],dp[j-w1[i]][k-w2[i]]+vi[i]);
}

分组背包

题目

有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

基本思路

基本特点:每组中只选一个物品或不选;定义dp[i][j]为前i组费用j时的最大价值,有

\[dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - w[bl[i][k]]] + v[bl[i][k]]) \]

注意,第二,三层不能对调,否则可能会多次选择同一物品(与01背包的倒序原因类似)

inline void fenzubeibao()
{
	for(rint i=1;i<=maxx;i++)//组数 
		for(rint j=V;j>=0;j--)
        	for(rint k=1;k<=num[i];k++)//组内个数 
            	if(j>=w[bl[i][k]])dp[j]=max(dp[j],dp[j-w[bl[i][k]]]+v[bl[i][k]]);
}

有依赖的背包

这种背包问题的物品间存在某种“依赖”的关系。也就是说,i依赖于j,表示若选物品i,则必须选物品j。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。

其它待更...

注意

“最多”与“恰好”

dp[][j]中j可表示“花费至多为j”或“花费刚好为j”,适应题目与处理方法也不同。有些要求能精确知道花费的题目就要把j设为后者。不同处理体现在“刚好”时的j不存在的情况对应的dp[][j]也不应被采取,比如说求最大值时“至多”可以初始化dp为0,“恰好”初始化-inf防止被选为答案或用来更新

输出方案

记录下每个状态的最优值是由状态转移方程的哪一项推出来的,换句话说,记录下它是由哪一个策略推出来的。便可根据这条策略找到上一个状态,从上一个状态接着向前推即可。还是以01背包为例,方程为

f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i])
再用一个数组g[i][j],设g[i][j]=0表示推出f[i][j]的值时是采用了方程的前一项(也即f[i][j]=f[i−1][j]),g[i][j]表示采用了方程的后一项。注意这两项分别表示了两种策略:未选第i个物品及选了第i个物品,最终状态为f[N][V]。
另外,采用方程的前一项或后一项也可以在输出方案的过程中根据f[i][j]的值实时地求出来,也即不须纪录g数组,将上述代码中的g[i][j]0改成f[i][j]f[i−1][j],g[i][j]1改成f[i][j]f[i−1][j−w[i]]+v[i]也可。

字典序最小

ans[]记录下后排序

求方案总数

一般只需将状态转移方程中的max改成sum即可。例如若每件物品均是完全背包中的物品,转移方程即为

\[dp[i][j] = Σdp[i−1][j],dp[i][j−w[i]] \]

初始条件f[0][0]=1。事实上,这样做可行的原因在于状态转移方程已经考察了所有可能的背包组成方案。

感谢并推荐博客1
博客2
背包九讲原文
干脆背包九讲.pdf

posted @ 2019-10-25 22:47  Thomastine  阅读(412)  评论(0编辑  收藏  举报