背包问题,是dp的一类经典问题,一类模板大致分为9类,但其本质只有2种类型(01背包与完全背包)
一.非背包做法:
题目描述
一个旅行者有一个最多能用m公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn.若每种物品只有一件求旅行者能获得最大总价值。
输入
第1行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);
第2至N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。
输出
仅一行,一个数,表示最大总价值。
分析一下,得出以下爆搜代码:
#include<bits/stdc++.h>
using namespace std;
typedef double dd;
int a[25][2],x;
int c[25],b[25],ans=-999,n;
void dfs(int k,int sy,dd jz)
{
if(k>n)
{
if(jz>ans)
{
ans=jz;
}
return;
}
if(sy>=a[k][0])
{
b[k]=1;
dfs(k+1,sy-a[k][0],jz+a[k][1]);//取当前的
b[k]=0;
}
dfs(k+1,sy,jz);//不取
}
int main()
{
cin>>x>>n;
for(int i=1;i<=n;i++)
cin>>a[i][0]>>a[i][1];
dfs(1,x,0);
cout<<ans<<endl;
return 0;
}
爆搜的复杂度可以到O(2^n),n>30是必爆时间的。
考虑优化:记忆化搜索,每次试一试当前物品,记录最大值,分类讨论取与不取
#include<bits/stdc++.h>
using namespace std;
int f[1001][1001] ,m ,ans;
struct node{
int t,jz;
}a[101];
int dfs(int ti,int k)//当前容量,递归层数
{
if(f[ti][k]!=-1) return f[ti][k];
if(k>m)
return f[ti][k] = 0;//边界
int ans1=-0x3f,ans2=-0x3f;
ans2=dfs(ti,k+1);
if(ti-a[k].t>=0)//满足条件,取
ans1=dfs(ti-a[k].t,k+1)+a[k].jz;
return f[ti][k]=max(ans1,ans2);//取和不取都试试
}
int main()
{
int t;
cin>>t>>m;
memset(f,-1,sizeof(f));
for(int i=1;i<=m;i++)
cin>>a[i].t>>a[i].jz;
cout<<dfs(t,1);
return 0;
}
AC 记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
二.dp正题
(1).如果记忆化学的扎实的话,很容易推出f(i)(j)=f(i-1)(j-1),f(i-1)(j-v[i]) (v[i]<=j<=m)(1<=i<=n) 的关系式
下面给出分析:
设f(i)(j)表示取前i个物品,以m为背包重量的最大值,则:
当前轮只与前一轮相关(前i-2轮是定值,假设前i-2轮一定最优)
如果不取,当前背包容量与前面一轮的背包容量值相同 (j相同),所以f(i)(j)=f(i-1)(j);
如果取,当前背包的容量要减去当前物品的重量 (j不同),并加上价值,所以f(i)(j)=f(i-1)(j-v[i])+c[i];
属性为max,故f(i)(j)=max{f(i-1)(j-1),f(i-1)(j-v[i])} (v[i]<=j<=m)(1<=i<=n)成立
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,w[201],c[201];
int f[35][201];
int main()
{
ios::sync_with_stdio(false);
cin>>m>>n;
for(int i=1;i<=n;i++) cin>>w[i]>>c[i];
for(int i=1;i<=n;i++){
for(int j=w[i];j<=m;j++){
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);
}
}
cout<<f[n][m];
return 0;
}
该程序时间复杂度为O(nm),比暴力强多了
(2)
认真阅读“当前轮只与前一轮相关(前i-2轮是定值,假设前i-2轮一定最优)”
所以可以两个数组来回做(滚动数组),空间复杂度是O(n)的
不过这东西是真鸡肋
t=i%2;
f[t][j]=max(f[t^1][j],f[t^1][j-w[i]]+c[i]);
思维难度有的
boss,降一维数组:
f(i)表示以i为背包容量的最大价值
70 3
71 100
69 1
1 2
这是输入数据
代码一:
cin >> t >> m;
for(int i = 1; i <= m; i++) cin >> w[i] >> v[i];
for(int i = 1; i <= m; i++){
for(int j = t; j >= 1; j--)
dp[j] = max(dp[j - w[i]] + v[i], dp[j]);
}
cout << dp[t];
输出 3
代码二:
int main(){
cin >> t >> m;
for(int i = 1; i <= m; i++) cin >> w[i] >> v[i];
for(int i = 1; i <= m; i++){
for(int j = 1; j <= t; j++)
dp[j] = max(dp[j - w[i]] + v[i], dp[j]);
}
输出0
为什么? 这两个代码的第二层循环的顺序改变了,也就是对钱数的循环顺序变了,为什么一个小小的操作会有这么大的影响呢?因为我们在更新dp[i]数组时,用的是dp[i]和dp[i - w[i]]这两个状态,假如我们从1更新到M,那么当我们更新dp[i]时,dp[i - w[i]]已经被更新过了,它已经不是原来的那个dp[i - w[i]]了,很有可能dp[i - w[i]]已经买了一包第i种,然后我们更新dp[i]时调用dp[i - w[i]],又买了一样的,这不符合01背包。
虽然不符合01背包但这符合一个物品可取多次的情况,即完全背包问题
int main(){
cin >> t >> m;
for(int i = 1; i <= m; i++) cin >> w[i] >> v[i];
for(int i = 1; i <= m; i++){
for(int j = 1; j <= t; j++)
dp[j] = max(dp[j - w[i]] + v[i], dp[j]);
}