背包问题小结
来自cainiao乱写的笔记
1)01背包问题
朴素算法下的状态转移方程为:f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i])
由于每层循环只用到了上一层的i
,故而可以优化为一维,但是由于j-v[i]
严格小于j
,即计算f[j]
时,f[j-v[i]]
已经算过,会被覆盖,所以枚举j
时需要从大到小。
所以最终的状态转移方程为:f[j] = max(f[j], f[j - v[i]] + w[i])
;
完整代码为:
#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 (int j = m; j >= v[i]; j -- )
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
2)完全背包问题
朴素算法下的状态转移方程为:f[i][j] = max(f[i][j],f[i-1][j-k*v[i]] + k*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]);
若优化到二维:
我们知道:
f[i][j]=max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w....)
f[i][j-v]=max( f[i-1][j-v], f[i-1][j-2v]+w....)
我们发现可以使用下面的f[i][j-v[i]]+w[i]
替换上面的后半部分式子
状态转移方程:f[i][j]=max(f[i-1][j],f[i][j-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][j-v[i]]+w[i]);
}
再次优化到一维:
不同于01背包问题的是,不需要从大到小遍历,直接遍历即可。
状态转移方程:f[j]=max(f[j],f[j-v[i]+w[i);
该情况下核心代码为:
for(int j=v[i];j<=m;j++)
f[j]=max(f[j],f[j-v[i]]+w[i]);
3)多重背包问题
朴素算法下的状态转移方程为:f[i][j] = max(f[i][j],f[i-1][j-v[i]*k] +k*w[i]);
该情况下核心代码为:
for(int i = 1;i<=n;i++)
for(int j = 0;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] +k*w[i]);
在多重背包中:
f[i , j ] = max( f[i-1,j] ,f[i-1,j-v]+w ,f[i-1,j-2v]+2w ,..... f[i-1,j-Sv]+Sw, )
f[i , j-v]= max( f[i-1,j-v] ,f[i-1,j-2v]+w, ..... f[i-1,j-Sv]+(S-1)w, f[i-1,j-(S+1)v]+Sw )
可以发现由于选择个数的限制多出了一项f[i-1,j-(S+1)v]+Sw
所以该题我们用到了二进制优化将时间复杂度从O(n^3)
降到O(n^2logS)
:
该思想为将大量物品分别以1,2,4,8,···,2^k,C的数量分别打包,而后将问题看作01背包去解决。
例如:将200划分为1,2,4,8,16,32,64,73的组合前七个数可构成0-127之间的所有数,加上最后的73,
即可构成0-200的所有数。但前提条件是要满足`c<2^(k+1)`
完整代码为:
#include<iostream>
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N];
int f[M];
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; // s要减小
k *= 2; // 组别里的个数增加
}
//剩余的一组
if(s>0)
{
cnt ++ ;
v[cnt] = a*s;
w[cnt] = b*s;
}
}
n = cnt ; //枚举次数正式由个数变成组别数
/*****01背包问题的一维优化*****/
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] << endl;
return 0;
}
4)分组背包问题:
朴素算法下的状态转移方程为:f[j] = max(f[j],f[j-v[i][k]]+w[i][k]);
完整代码为:
#include<iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n,m;
int v[N][N],w[N][N];
int f[N];
int s[N];
int main(){
cin >> n >> m;
for(int i = 1;i<=n;i++)
{ cin>>s[i];
for(int j = 0;j<s[i];j++)
cin>>v[i][j]>>w[i][j];
}
for(int i = 1;i<=n;i++)
for(int j = m;j>=0;j--)
for(int k = 0; k<s[i];k++)
if(v[i][k]<= j) f[j] = max(f[j],f[j-v[i][k]]+w[i][k]);
cout << f[m];
return 0;
}
Tips:枚举 j 时,如果用到了上一层元素则从大到小枚举(01背包),否则从小到大枚举即可。
最后,放一块看看吧:
1)f[i][j]= max(f[i][j],f[i-1][j-v[i]]+w[i])
f[j] = max(f[j], f[j - v[i]] + w[i])
2)f[i][j] = max(f[i][j],f[i-1][j-k*v[i]] + k*w[i])
f[i][j] =max(f[i-1][j],f[i][j-v[i]]+w[i])
f[j]=max(f[j],f[j-v[i]+w[i)
3)f[i][j] = max(f[i][j],f[i-1][j-v[i]*k] +k*w[i])
4)f[j] = max(f[j],f[j-v[i][k]]+w[i][k])