背包问题
P01: 01背包问题
1.1 问题
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
1.2 基本思路
在不超过背包容量的情况下,最多能获得多少价值
子问题状态:f[j]:表示前i件物品放入容量为j的背包得到的最大价值
状态转移方程:f[j] = max{f[j],f[j - weight[i]] + value[i]}
初始化:f数组初始状态设置为0
1.3 代码部分
#include <bits/stdc++.h>
using namespace std;
const int N = 4;//物品个数
const int V = 5;//背包最大容量
int weight[N] = {1,2,2,3};//物品重量
int value[N] = {25,10,20,30};//物品价值
int f[V + 1];
int ZeroOnePack()
{
memset(f,0,sizeof(f));
//递推
for (int i = 0;i < N;i++) //枚举物品(注意物品的编号)
{
for (int j = V;j >= weight[i];j--) //枚举背包容量,防越界,j下限为 weight[i]!!!
{
f[j] = max(f[j],f[j - weight[i]] + value[i]);
cout << j << " " << f[j] << endl;
}
}
return f[V];
}
int main()
{
cout << ZeroOnePack() << endl;
return 0;
}
1.4 初始化细节问题
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。
有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。
如果是第一种问法,要求恰好装满背包,那么在初始化时除了F[0]为0,其它F[1…V ]均设为−∞,这样就可以保证最终得到的F[V ]是一种恰好装满背包的最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将F[0…V ]全部设为0。 这是为什么呢?可以这样理解:初始化的F数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可以在什么也不装且价值为0的情况下被“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,应该被赋值为-∞了。
如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的
价值为0,所以初始时状态的值也就全部为0了。
P02: 完全背包问题
2.1 问题
有N种物品和一个容量为V 的背包,每种物品都有无限件可用。放入第i种 物品的耗费的空间是Ci,得到的价值是Wi。求解:将哪些物品装入背包,可使 这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
2.2 基本思路
可以考虑将完全背包转换为01背包问题来解。
在解决01背包问题的过程中,如果背包的容量倒着从V开始减,那么就意味着,每个物品每次只能选择一次,但是反过来就相当于每个物品只要体积之和不超过指定的V,就可以一直取下去,从而就可以看成每个物品都有无限多件。
2.3 代码部分
int CompletePack()
{
memset(f,0,sizeof(f));
//递推
for (int i = 0;i <= N; ++ i) //枚举物品(注意物品的编号)
{
for (int j = weight[i];j <= V; ++ j) //枚举背包容量,防越界,j下限为 weight[i]!!!
{
f[j] = max(f[j],f[j - weight[i]] + value[i]);
cout << j << " " << f[j] << endl;
}
}
return f[V];
}
2.4 一个简单的优化
完全背包问题有一个很简单有效的优化,是这样的:若两件物品i、j满足Ci ≤ Cj且Wi ≥ Wj,则将可以将物品j直接去掉,不用考虑。不过一般是用不到的。
P03: 多重背包问题
3.1 问题
有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci,价值是Wi。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
3.2 基本思路
把第i种物品换成Mi件01背包中的物品,则得到了物品数为ΣMi的01背包问题。
我们可以通过二进制的拆分方法对其优化。对每i件物品,拆分的策略为:新拆分的物品的重量等于1件,2件,4件,…,(2^(k - 1)),Num[i] - (2^(k - 1))件,其中k 是满足Num[i] - 2^k + 1 > 0 的最大整数。
注意:
(1)最后一个物品的件数的求法和前面不同,其直接等于 该物品的最大件数 - 前面已经分配之和。
(2)分成的这几件物品的系数和为Num[i],表明第i种物品取的件数不能多于Num[i]。
举例:某物品为13件,则其可以分成四件物品,其系数为1,2,4,6.这里k = 3。
使用二进制的前提还是使用二进制拆分能保证对于0,Num[i]间的每一个整数,均可以用若干个系数的和表示。
3.3 代码部分
/*
01背包,v为降序
f[v]:表示把前i件物品放入容量为v的背包中获得的最大收益。
f[v] = max(f[v],f[v - Weight[i]] + Value[i]);
*/
void ZeroOnePack(int nWeight,int nValue)
{
for (int v = V; v >= nWeight; v--)
{
f[v] = max(f[v],f[v - nWeight] + nValue);
}
}
/*
完全背包,v为增序。
f[v]:表示把前i件物品放入容量为v的背包中获得的最大收益。
f[v] = max(f[v],f[v - Weight[i]] + Value[i]);
*/
void CompletePack(int nWeight,int nValue)
{
for (int v = nWeight; v <= V; v++)
{
f[v] = max(f[v],f[v - nWeight] + nValue);
}
}
int MultiplePack()
{
int k = 1;
int nCount = 0;
for (int i = 1; i <= N; i++)
{
if (weight[i] * num[i] >= V)
{
//此时满足条件Weight[i] * Num[i] >= V时,
//完全背包:该类物品相当于是无限供应,直到背包放不下为止。
CompletePack(weight[i],value[i]);
}
else
{
k = 1;
nCount = num[i];
while(k <= nCount)
{
ZeroOnePack(k * weight[i],k * value[i]);
nCount -= k;
k *= 2;
}
ZeroOnePack(nCount * weight[i],nCount * value[i]);
}
}
return f[V];
}
以上的代码可直接拿做多重背包的模板(记得初始化),但是重要的还是思想,注意灵活运用。
3.4 实战部分
HDOJ 2192 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活
**题目概述:**多重背包模板题目
代码部分:
#include <iostream>
#include <string.h>
using namespace std;
const int maxn = 1005;
int f[maxn + 1];
int weight[maxn + 1], value[maxn + 1], num[maxn + 1];
int V,N;
/*
01背包,v为降序
f[v]:表示把前i件物品放入容量为v的背包中获得的最大收益。
f[v] = max(f[v],f[v - Weight[i]] + Value[i]);
*/
void ZeroOnePack(int nWeight,int nValue)
{
for (int v = V; v >= nWeight; v--)
{
f[v] = max(f[v],f[v - nWeight] + nValue);
}
}
/*
完全背包,v为增序。
f[v]:表示把前i件物品放入容量为v的背包中获得的最大收益。
f[v] = max(f[v],f[v - Weight[i]] + Value[i]);
*/
void CompletePack(int nWeight,int nValue)
{
for (int v = nWeight; v <= V; v++)
{
f[v] = max(f[v],f[v - nWeight] + nValue);
}
}
int MultiplePack()
{
int k = 1;
int nCount = 0;
for (int i = 1; i <= N; i++)
{
if (weight[i] * num[i] >= V)
{
//此时满足条件Weight[i] * Num[i] >= V时,
//完全背包:该类物品相当于是无限供应,直到背包放不下为止。
CompletePack(weight[i],value[i]);
}
else
{
k = 1;
nCount = num[i];
while(k <= nCount)
{
ZeroOnePack(k * weight[i],k * value[i]);
nCount -= k;
k *= 2;
}
ZeroOnePack(nCount * weight[i],nCount * value[i]);
}
}
return f[V];
}
int main()
{
int t,i;
cin>>t;//会输入几组数据
while(t--)
{
memset(f, 0, sizeof(f));
cin>>V>>N;//v:背包容量 n:物体个数
for(i = 1 ; i <= N; i++)//从i = 1开始输入!所以最后输出f[V]不是f[V-1]!
cin>>weight[i]>>value[i]>>num[i];
cout<<MultiplePack()<<endl;
}
return 1;
}
附加题: HDOJ 1059 Dividing
P04: 混合三种背包问题
4.1 问题
如果将前面1、2、3中的三种背包问题混合起来。也就是说,有的物品只可
以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取
的次数有一个上限(多重背包)。
4.2 01背包与完全背包的混合
考虑到01背包和完全背包中给出的伪代码只有一处不同,故如果只有两类
物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个
物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度
是O(V N)。伪代码如下:
for i = 1 to N
if 第i件物品属于01背包
for v = V to Ci
F[v] = max(F[v], F[v − Ci] + Wi)
else if 第i件物品属于完全背包
for v = Ci to V
F[v] = max(F[v], F[v − Ci] + Wi)
4.3 三种背包混合
for i = 1 to N
if 第i件物品属于01背包
ZeroOnePack(F,Ci ,Wi )
else if 第i件物品属于完全背包
CompletePack(F,Ci ,Wi )
else if 第i件物品属于多重背包
MultiplePack(F,Ci ,Wi ,Ni )
P05: 二维费用的背包问题
5.1 问题
二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为w[i]。
5.2 基本思路
费用加了一维,只需状态也加一维即可。设f[i][v][u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值。状态转移方程就是:
f[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]}
如前述方法,可以只使用二维的数组:当每件物品只可以取一次时变量v和u采用逆序的循环,当物品有如完全背包问题时采用顺序的循环。当物品有如多重背包问题时拆分物品。根据题目要求使用相应的背包方法。
5.3 实战部分
题目链接: HDOJ 2159 FATE
题目描述: 现在的问题是,xhd升掉最后一级还需n的经验值,xhd还留有m的忍耐度,每杀一个怪xhd会得到相应的经验,并减掉相应的忍耐度。当忍耐度降到0或者0以下时,xhd就不会玩这游戏。xhd还说了他最多只杀s只怪。请问他能升掉这最后一级吗?
样例输入: 输入数据有多组,对于每组数据第一行输入n,m,k,s(0 < n,m,k,s < 100)四个正整数。分别表示还需的经验值,保留的忍耐度,怪的种数和最多的杀怪数。接下来输入k行数据。每行数据输入两个正整数a,b(0 < a,b < 20);分别表示杀掉一只这种怪xhd会得到的经验值和会减掉的忍耐度。(每种怪都有无数个)
样例输出: 输出升完这级还能保留的最大忍耐度,如果无法升完这级输出-1。
测试数据:
Sample Input
10 10 1 10
1 1
10 10 1 9
1 1
9 10 2 10
1 1
2 2
Sample Output
0
-1
1
**解题思路:**从题目可以看出,怪物可以无限刷,那么这就是完全背包问题。因为题目中涉及到两个条件(忍耐度,刷怪数量),可以看出这是一个二维费用完全背包题目。
我们应该用一个二维的dp数组存放在一定的(刷怪数量和和忍耐度)的状态,得到的经验是多少。
状态转移方程:dp[j][z] = max(dp[j - 1][z - b[i]] + a[i], dp[j][z]);
(j为刷怪数量,z为忍耐度,b[i]为第i个怪消耗掉的忍耐度,a[i]代表第i个怪的得到的经验)。最后注意是完全背包就可以了。
代码部分:
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn = 1e2 + 10;
int dp[maxn][maxn];
int a[maxn], b[maxn];
int main()
{
int n, m, k, s;
while(~scanf("%d%d%d%d", &n, &m, &k, &s))
{
for(int i = 1; i <= k; ++ i)
{
scanf("%d%d", &a[i], &b[i]);
}
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= k; ++ i)//怪的种类
{
for(int z = b[i]; z <= m; ++ z)//消耗的忍耐(完全背包)
{
for(int j = 1; j <= s; j ++) //打怪的数量 必须从1到s
{
dp[j][z] = max(dp[j - 1][z - b[i]] + a[i], dp[j][z]);
}
}
}
if(dp[s][m] >= n)
{
for(int i = 0; i <= m; ++ i)
{
if(dp[s][i] >= n)
{
printf("%d\n", m - i);
break;
}
}
}
else
{
printf("-1\n");
}
}
return 0;
}
P06: 分组背包问题
6.1 问题
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
6.2 基本思路
这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有f[k][v] = max{ f[k-1][v], f[k-1][v-c[i]]+w[i] | 物品i属于第k组 }。
6.3 实战部分
HDOJ 3535 AreYouBusy
**题目描述:**有n个组的工作,有t的时间去做
对于组有三种类型
2随意,拿或者不拿,选其中多少都可以
1最多选一个
0至少选一个
每组每件事有花费的时间和收益
取尽量高的收益
解题思路:
经典混合背包
题目给了很多类别的物品。用 数组dp[i][j],表示第i组,时间为j时的快乐值。每得到一组工作就进行一次DP,所以dp[i]为第i组的结果。
第一类(01背包坑点多),至少选一项,即必须要选,那么在开始时,对于这一组的dp的初值,应该全部赋为负无穷,这样才能保证不会出现都不选的情况。
状态转移方程:dp[i][j]=max(dp[i][j],max(dp[i][j-w[x]]+p[x],dp[i-1][j-w[x]]+p[x]));
dp[i][j]: 是不选择当前工作;
dp[i-1][j-w[x]]+p[x]: 第一次在本组中选物品,由于开始将该组dp赋为了负无穷,所以第一次取时,必须由上一组的结果推知,这样才能保证得到全局最优解;
dp[i][j-w[x]]+p[x]:表示选择当前工作,并且不是第一次取;
第二类(分组背包),最多选一项,即要么不选,一旦选,只能是第一次选。
状态转移方程:dp[i][j]=max(dp[i][j],dp[i-1][j-w[x]]+p[x]);
由于要保证得到全局最优解,所以在该组DP开始以前,应该将上一组的DP结果先复制到这一组的dp[i]数组里,因为当前组的数据是在上一组数据的基础上进行更新的。
第三类(01背包),任意选,即不论选不选,选几个都可以。
状态转移方程为:dp[i][j]=max(dp[i][j],dp[i][j-w[x]]+p[x]);
同样要保证为得到全局最优解,先复制上一组解,数据在当前组更新。
代码部分:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
int n,m,sum;
int w[110],p[110];
int dp[110][110];
int main()
{
while(~scanf("%d%d",&n,&sum))
{
memset(dp,0,sizeof(dp));
int i,j,k,g;
for(i=1; i<=n; i++)
{
scanf("%d%d",&m,&g);
for(k=1; k<=m; k++)
scanf("%d%d",&w[k],&p[k]);
if(g==0)
{
for(j=0; j<=sum; j++) //当前组初始化
dp[i][j]=-INF;
for(k=1; k<=m; k++)
for(j=sum; j>=w[k]; j--)
dp[i][j]=max(dp[i][j],max(dp[i][j-w[k]]+p[k],dp[i-1][j-w[k]]+p[k]));
}
else if(g==1)
{
for(j=0; j<=sum; j++) //当前组初始化
dp[i][j]=dp[i-1][j];
for(k=1; k<=m; k++)
for(j=sum; j>=w[k]; j--)
dp[i][j]=max(dp[i][j],dp[i-1][j-w[k]]+p[k]);
}
else if(g==2)
{
for(j=0; j<=sum; j++) //当前组初始化
dp[i][j]=dp[i-1][j];
for(k=1; k<=m; k++)
for(j=sum; j>=w[k]; j--)
dp[i][j]=max(dp[i][j],dp[i][j-w[k]]+p[k]);
}
}
dp[n][sum]=max(dp[n][sum],-1); //没有完成任务的值都为负的,做输出调整,输出-1
printf("%d\n",dp[n][sum]);
}
return 0;
}
P07: 有依赖背包问题
7.1 问题
这种背包问题的物品间存在某种“依赖”的关系。也就是说,i依赖于j,表示若选物品i,则必须选物品j,问选择n件物品,可以得到的最大价值。举个例子,在大学中,要修很多功课,但是这些功课有的都有先行课,比如在学C++ 之前,必须要学C++,这时候C++ 就依赖于C,当C没有学习的时候,就没有办法去学习C++。
7.2 基本思路
首先先简化一下问题:先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。
这时候我们可以将被依赖的物品称为“主件”,依赖主件的物品称为“附件”。
首先先分析一下物品可以怎么选择:对于每个主件和其对应的附件,主件可以分为选与不选。附件可以选一个,两个……普通的背包是无法解决这样的问题。
**解决问题方法:**我们可以先将主件对应的附件做一个01 背包的处理。这时候对于每个主件就可以转换成分组背包的问题,每组中包含(不选主件,选主件不选附件,选主件选一个附件,选主件选两个附件……)。
**一般问题方案:**依赖关系以图论中“森林”的形式给出(森林即多叉树的集合),也就是说,主件的附件仍然可以具有自己的附件集合,限制只是每个物品最多只依赖于一个物品(只有一个主件)且不出现循环依赖。
事实上,这是一种树形DP,其特点是每个父节点都需要对它的各个儿子的属性进行一次DP以求得自己的相关属性。
7.3 实战部分
HDOJ 3449 Consumer
**题目意思:**有N个箱子,每个箱子需要花费Ai的代价
箱子里面有K个物品,K个物品你可以选择买任意个,但每个物品只能买一个,每个物品有相应的花费和价值
你又M的大洋,最后问最多能得到多少价值物品。
**解题思路:**先对每组选择里的所有物品进行0-1背包处理,但背包容量为(总容量-盒子容量);然后跟上一组的状态比较来决定这一组选择 是选还是不选,取其中的较大值。
代码部分:
#include<stdio.h>
#include<string.h>
const int maxn=100010;
int dp[maxn];
int tmp[maxn];
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int n,sum,p,m,w,v,i,j,k;
while(scanf("%d%d",&n,&sum)!=EOF)
{
memset(dp,0,sizeof(dp));
for(i=0; i<n; i++)
{
scanf("%d%d",&p,&m);
memcpy(tmp,dp,sizeof(dp));//继承前面的
for(j=0; j<m; j++)
{
scanf("%d%d",&w,&v);
for(k=sum-p; k>=w; k--) //先将附件进行1次01背包
tmp[k]=max(tmp[k],tmp[k-w]+v);
}
for(j=p; j<=sum; j++) //更新能更新的
dp[j]=max(dp[j],tmp[j-p]); //分组背包
}
printf("%d\n",dp[sum]);
}
return 0;
}
HDOJ 1561 The more, The Better
题目意思: ACboy很喜欢玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中ACboy允许攻克M个城堡并获得里面的宝物。但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先攻克其他某一个特定的城堡。你能帮ACboy算出要获得尽量多的宝物应该攻克哪M个城堡吗?
每个测试实例首先包括2个整数,N,M.(1 <= M <= N <= 200);在接下来的N行里,每行包括2个整数,a,b. 在第 i 行,a 代表要攻克第 i 个城堡必须先攻克第 a 个城堡,如果 a = 0 则代表可以直接攻克第 i 个城堡。b 代表第 i 个城堡的宝物数量, b >= 0。
解题思路:(有依赖背包 || 树形DP)
定义状态dp[i][j] : 当前i节点及其子树下最多选择j个城市的最大值为dp[i][j];
我们考虑到特殊状态:i节点下没有孩子那么dp[i][2,3,4,5…]均为-1(因为多选总比少选好,并且选择完后城市总是有剩余)
- 判断当前节点P有没有孩子,如果有则令当前节点为P重复(1)操作,如果没有则到(2)操作;
- 将当前节点P的状态更新到期父节点上,
更新操作为dp[P'father][i] = max(dp[P'father][i], dp[P'father][j]+dp[P][k])
(j + k = i ,j>0,k>0,2<=i<=max_cost,对于每一个i遍历每一种(j,k)组合)
这里的dp[P’father][j] j个城市一定是没有包括P城市的其他j个城市的最大值
直到遍历到root节点即可(dp[0][i])
3.输出dp[0][max_cost]
max_cost 为题目中所给出的最多取几个城市
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
[i]:v 表示 第i个节点的价值为v; [0]root没有价值相当于[0]:0
代码部分:
#include <bits/stdc++.h>
using namespace std;
const int maxn=210;
vector<int>g[maxn];
int dp[maxn][maxn],v[maxn];
void dfs(int n,int m)
{
int siz=(int)g[n].size();
dp[n][1]=v[n];
for(int i=0; i<siz; i++)
{
int v=g[n][i];
if(m>=1) dfs(v,m-1);//递归先对子树处理
for(int j=m; j>=1; j--)
{
for(int k=1; k<=j; k++)
{
dp[n][j+1] = max(dp[n][j+1],dp[n][j+1-k]+dp[v][k]);
}
}
}
}
int main()
{
int n,m,x;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0) break;
m++;
for(int i=0; i<=n; i++) g[i].clear();
memset(dp,0,sizeof(dp));
memset(v,0,sizeof(v));
for(int i=1; i<=n; i++)
{
scanf("%d%d",&x,&v[i]);
g[x].push_back(i);
}
dfs(0,m);
cout<<dp[0][m]<<endl;
}
return 0;
}
P08: 泛化背包问题
8.1 问题
考虑这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它的费用而变化。这就是泛化物品的概念。在背包容量为V的背包问题中,泛化物品是一个定义域为0…V中的整数的函数h,当分配给它的费用为v时,能得到的价值就是h(v)。
8.2 基本思路
如果面对两个泛化物品h和l,要用给定的费用从这两个泛化物品中得到最大的价值,怎么求呢?事实上,对于一个给定的费用v,只需枚举将这个费用如何分配给两个泛化物品就可以了。同样的,对于0…V的每一个整数v,可以求得费用v分配到h和l中的最大价值f(v)。也即f(v)=max{h(k)+l(v-k)|0<=k<=v}。
这个运算的时间复杂度取决于背包的容量,是O(V^2)。
###P09:背包问题变化
9.1 背包的第K优解问题
问题描述 DD 和好朋友们要去爬山啦!他们一共有 K 个人,每个人都会背一个包。这些包的容量是相同的,都是 V。可以装进背包里的一共有 N 种物品,每种物品都有给定的体积和价值。
在 DD 看来,合理的背包安排方案是这样的:
每个人背包里装的物品的总体积恰等于包的容量。
每个包里的每种物品最多只有一件,但两个不同的包中可以存在相同的物品。
任意两个人,他们包里的物品清单不能完全相同。
在满足以上要求的前提下,所有包里的所有物品的总价值最大是多少呢?
**解题思路:**用q1和q2记录,保持递增,合并成前k优解。 要使得背包恰好装满,就要赋初值-maxint,而当背包容量为0时,赋值0。这题和典型的01背包求最优解不同,是要求第k大的解,所以,最直观的想法就是在01背包的基础上再增加一维,用来保存前k大小的数,然后在递推时,根据前一个状态的前k大小的数推出下一个阶段的前k个数保存下来。
代码部分:
#include<iostream>
using namespace std;
long long dp[51][50001];
int q1[51];
int q2[51];
int main()
{
int k,v,n;
int value,weight;
cin>>k>>v>>n;
for(int i=0; i<=k; i++)
for(int j=0; j<=v; j++)
dp[i][j]=INT_MIN;
dp[1][0]=0;
for(int i=1; i<=n; i++)
{
cin>>weight>>value;
for(int j=v; j>=weight; j--)
{
for(int w=1; w<=k; w++)
{
q1[w]=dp[w][j];
q2[w]=dp[w][j-weight]+value;
}
int h1=1,h2=1,h=0;
while(h<k)
{
h++;
if(q1[h1]>q2[h2])
{
dp[h][j]=q1[h1];
h1++;
}
else
{
dp[h][j]=q2[h2];
h2++;
}
}
}
}
int ans=0;
for(int i=1; i<=k; i++)
ans+=dp[i][v];
cout<<ans<<endl;
return 0;
}