01背包(每个物品只有一件)
通过一个题目来了解01背包,这也是01背包的模板题。
题目
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。输出格式
输出一个整数,表示最大价值。数据范围
0<N,V≤1000,0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5输出样例
8
思路:
- 令dp[ i ][ j ]表示:对于前 i 件物品,体积为 j 时的价值最大值。
- 取得最大值时,可能取了第 i 件物品,也可能没有取第 i 件物品
- 如果取了第 i 件物品,那么dp[ i ][ j ]=dp[ i-1 ][ j-v[ i ] ]+w[ i ]
- 如果没有取第 i 件物品,那么dp[ i ][ j ]=dp[ i-1 ][ j ]
- dp[ i ][ j ]=max(dp[ i-1 ][ j-v[ i ] ]+w[ i ] ,dp[ i-1 ][ j ])
- 正常情况下写代码需要dp数组是二维的,那么,我们通过观察第五条的式子可以看出dp[ i ][ j ]仅与前 i-1 件物品取值有关。这样的话,就可以将二维数组优化为一维数组。
具体实现
#include<iostream>
using namespace std;
const int N=1e3+10;
int n,sv;
int v[N],w[N];
int dp[N];
int main()
{
cin>>n>>sv;
for(int i=1;i<=n;i++)
cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=sv;j>=v[i];j--)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
cout<<dp[sv]<<endl;
return 0;
}
完全背包(每个物品有无数件)
题目
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。输出格式
输出一个整数,表示最大价值。数据范围
0<N,V≤1000,0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5输出样例
10
思路
- dp[ i ][ j ]表示对于前i件物品,体积为j时能获得的最大价值
- 对于第 i 件物品可以不取,也可以取1件、两件…一直取到背包体积放不下
- 那么dp[ i ][ j ]=max(dp[ i-1 ][ j ],dp[ i-1 ][ j-v[ i ] ]+w[ i ],dp[ i-1 ][ j-2v[ i ] ]+2*w[ i ]…)
- 如果令上式的j=j-v[ i ],那么上式可以转化为dp[ i ][ j-v[ i ] ]=max(dp[ i-1 ][ j-v[ i ] ],dp[ i-1 ][ j-2v[ i ] ]+w[ i ],dp[ i-1 ][ j-3v[ i ] ]+2*w[ i ]…)
- 通过观察第三条和第四条可以得出dp[ i ][ j ]=max(dp[ i-1 ][ j ],dp[ i ][ j-v[ i ] ]+w[ i ])
具体实现
#include<iostream>
using namespace std;
const int N=1e3+10;
int n,sv;
int v[N],w[N];
int dp[N];
int main()
{
cin>>n>>sv;
for(int i=1;i<=n;i++)
cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
{
for(int j=v[i];j<=sv;j++)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
cout<<dp[sv]<<endl;
return 0;
}
多重背包问题(每个物品有s[ i ]件)
对于多重背包问题有三种解法
- 将s[ i ]拆成s[ i ]份,然后套用01背包的解决方法即可
- 通过二进制优化上一种解法,将每种物品拆分成 ⌊ log 2 s [ i ] ⌋ \lfloor \log_2s[ i ] \rfloor ⌊log2s[i]⌋+1件,然后用01背包解决(举个例子。假设s[ i ]=10,那么可以取第i件物品的数量为0~10中的任意一个数,那么可以将10用4个数表示,分别为1,2,4,3 )
- 用单调队列优化。每个物品都有一个体积v,价值w和数量s,对于体积为k的背包,dp[ k ]只能从dp[ k-v ]+w、dp[ k-2v ]+2w 、dp[ k-3v ]+3w…dp[ k-tv ]+tw(满足t<=s&&k-tv>=0)转换而来。那么我们可以根据v的值来对体积进行分类,体积%v的值相等的为一类。对于每一类,其中的每一个体积都只需要找到满足转换条件的式子中的最大值即可。而找这个最大值可以用单调队列来进行优化。
题目
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。输入格式
第一行两个整数N,V ( 0 < N ≤ 1000, 0 < V ≤ 20000),用空格隔开,分别表示物品种数和背包容积。接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。输出格式
输出一个整数,表示最大价值。数据范围
0<N≤1000
0<V≤20000
0<vi,wi,si≤20000
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2输出样例
10
这题的数据量只能用单调队列优化进行求解
#include<iostream>
#include<cstring>
using namespace std;
const int N=2e4+10;
int n,sv;
int v,w,s;
int ans[N],pre[N],ls[N];
int main()
{
cin>>n>>sv;
for(int i=1;i<=n;i++)
{
cin>>v>>w>>s;
memcpy(pre,ans,sizeof ans);
for(int j=0;j<v;j++)
{
int hh=0,tt=-1;
for(int k=j;k<=sv;k=k+v)
{
while(hh<=tt&&k-s*v>ls[hh])hh++;
if(hh<=tt)ans[k]=max(ans[k],pre[ls[hh]]+(k-ls[hh])/v*w);
while(hh<=tt&&pre[ls[tt]]+(k-ls[tt])/v*w<=pre[k])tt--;
ls[++tt]=k;
}
}
}
cout<<ans[sv]<<endl;
return 0;
}
同时也给出二进制优化的实现
#include<iostream>
#include<vector>
using namespace std;
const int N=2010;
struct node
{
int v;
int w;
};
int n,sv;
int dp[N];
int main()
{
cin>>n>>sv;
vector<node>c;
node ls;
int v,w,s;
int sum;
for(int i=1;i<=n;i++)
{
cin>>v>>w>>s;
sum=0;
for(int j=1;j<s;j=j*2)
{
if(sum+j>s)break;
sum+=j;
ls.v=j*v;
ls.w=j*w;
c.push_back(ls);
}
if(sum<s)
{
sum=s-sum;
ls.v=sum*v;
ls.w=sum*w;
c.push_back(ls);
}
}
int len=c.size();
for(int i=0;i<len;i++)
{
for(int j=sv;j>=c[i].v;j--)
{
dp[j]=max(dp[j],dp[j-c[i].v]+c[i].w);
}
}
cout<<dp[sv]<<endl;
return 0;
}