趁此机会整理一下思路。
背包问题合集
01背包
P1048 [NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
看这个图应该就可以理解01背包问题了
需要注意的一个点
1.f[i-1][j-v[i]]+w[i]下标不要越界,反映到题目的意思是,如果我么要选择第i个物品,前提条件就是在上一步处理完i-1情况下,背包的空间还可以放得下第i个物品
二维
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
cout<<f[n][m]<<endl;
return 0;
}
一维优化
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
从这一段可以看出我们每次更新状态的时候,只用到了上一级的状态,打表来看会更直接。
那么是否可以使用滚动数组来优化捏?答案是肯定的。
上文橘色高光注意!
我们观察一下j-v[i]肯定是小于j的,如果还是按正向更新的话,更新j时所使用的j-v[i]状态已经在前面更新过一次了。
跟简单,我们只需要j-v[i]在后面更新就可以了,很容易想到,反向遍历
这就得出了一维优化
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N],f[N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m];
return 0;
}
完全背包
3. 完全背包问题 - AcWing题库
与01背包问题相比,这里的物品有无限多个了。那么只需要小小地改动一下集合划分了。
与上文一样注意一下标越界(合法性)问题。k*v[i]<=j
就这样一个三重循环就出来啦!
朴素
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k*v[i]<=j;k++){
f[i][j]=max(f[i][j,f[i-1][j-k*v[i]+k*w[i]);
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
优化
这里的优化不是怎么看得出来。
那我们来做一个小小的推导,前面都是拿i-1 和i 两种相邻状态作比较。
这次也是
这里就不是利用上一个i-1状态,而是同一行前面的状态了。(从打表来看)
suoyi 01问题规避的东西我们正好需要
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int f[N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++){
for(j=v[i];j<=m;j++){
f[j]=max(f[j],f[j-v[i]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
比较期待多重背包的优化wwww
多重背包
与01、完全背包相比,多重背包依旧是在物品数量上面有了变化。
物品的数量变成了固定的数目。
朴素算法
#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int n,m,s[N],v[N],w[N],f[N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=0;k<=s[i]&&k*v[i]<=j;k++){
f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
朴素算法和之前的很像就不赘述了。
优化
这里的数据太大,会TLE。
采用的方式是二进制优化。
可以想一下计算机内部要将数字转化成二进制来存储,二进制只有0和1不就对应着01背包问题吗?
由此我们有了一种算法,将每件物品的数目转化成二进制存储。
24=2^4+8 =2^4+2^3 前 24次 后4次
65=2^6+1=2^6+2^0 前 65次 后 5次
1+2^1+2^2+2^3......+2^(k-1)=2^k -1
当然可以得出n----> ceil ln(n) (好像有一个函数ceil 是向上取整 floor是向下取整 )\
www,不知道怎么说。
0~n都可以用ln(n)位二进制来表示
依次,将每个物品的数量都可以打包成2^0 2^1......2^ln(n)+c种01背包问题。
复杂度就转化成了N*M*ln(n)
#include<iostream>
#include<algorithm>
using namespace std;
const int N=25000,M=2100;
int n,m;
int v[N],w[N];
int f[N];
int main(){
cin>>n>>m;
int cnt=0;
for(int i=1;i<=n;i++){
int a,b,s;
cin>>a>>b>>s;
int k=1;
while(k<=s){
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
s-=k;
k*=2;
}
if(s>0){
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
}
n=cnt;
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[m];
return 0;
}