目录
一、【NOIP2001】装箱问题
【动画演示真的yyds好嘛^^】
重中之重 状态转移方程 f[i][j] = f[i-1][j] || f[i-1][ j-v[i] ] 其实就是逆着想;
对第 i 个物品就两种选择:要 或 不要;
在 f[i-1][ j-v[i] ] 状态时 有,可以在此基础上放入目前的i 号物品,得到 f[i][j] 有;->此为“要”的选择;另一种情况,在上一次,即 f[i-1][j] 时已经有(意为有 j 体积了),这一轮第 i 个物品不要,所以体积还是原来的 j , 即 f[i][j] ;
其中有 “继承” 上一轮状态 并新添本次选择了的物品后的状态 的感觉。
理解了后 用最易懂的二维数组版写:
1.)二维基础版:
#include<iostream>
using namespace std;
int v,n,a[40],f[40][20010];
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>v>>n;
f[0][0]=1;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
for(int j=0;j<=v;j++){
if(j>=a[i])f[i][j]=(f[i-1][j]||f[i-1][j-a[i]]);
else f[i][j]=f[i-1][j];
}
}
for(int i=v;i>=0;i--){
if(f[n][i]){
cout<<v-i;
return 0;
}
}
}
2.)01滚动
更新第0行后,更新第1行,再要更新第2行的时候,发现只与它的前一行也就是第1行有关,第0行已经没用了,这时将第2行装到第0行里,后面再要写第3行时,写到第1行里……依此类推,也就是i , i-1变成了i%2, (i-1)%2等等
//01滚动
#include<iostream>
using namespace std;
int v,n,a[40],f[2][20010];
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>v>>n;
f[0][0]=1;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
for(int j=0;j<=v;j++){
if(j>=a[i])f[i%2][j]=(f[(i-1)%2][j]||f[(i-1)%2][j-a[i]]);
else f[i%2][j]=f[(i-1)%2][j];
}
}
for(int i=v;i>=0;i--){
if(f[n%2][i]){ // 注意最后判断的数组是n%2不一定是1
cout<<v-i;
return 0;
}
}
}
3.)“就地滚动”
//就地滚动 倒序更新
#include<iostream>
using namespace std;
int v,n,a[40],f[20010];
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>v>>n;
f[0]=1;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
for(int j=v;j>=a[i];j--){
f[j]=f[j]||f[j-a[i]]; //不要落写"f[j]||"
}
}
for(int i=v;i>=0;i--){
if(f[i]){
cout<<v-i;
return 0;
}
}
}
01背包
完全背包
多重背包
二维费用背包问题
分组背包
【特别注意 “所有的 i 属于组k ”的循环要在V的循环里面,因为要保证这一组的每个物品都是只选它时的情况,如果k循环放到了外面,那就会变成同一组中的物品a在基于同组物品b已选的情况下转移得到,而事实上它们会起冲突……】