背包DP
01 背包
有 \(N\) 件物品和一个容量为 \(M\) 的背包。第 \(i\) 件物品的重量是 \(W_i\),价值是 \(D_i\)。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
设DP状态为\(f_{i,j}\)表示在只能放前\(i\)个物品,容量为\(j\)的价值情况
考虑已经放了\(i-1\)个物品背包剩余容量为\(j\),对于第\(i\)个物品在不放入背包的情况下最大价值状态为\(f_{i-1,j}\)
反之\(f_{i-1,j - v_i} + w_i\)
可得状态转移方程
\[\huge f_{i,j} = max(f_{i-1,j},f_{i-1,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 - 1][j - v[i]] + w[i]);
}
观察方程可以发现\(f_{i,j}\)之用到了\(f_{i-1,j}\)这一层,且\(j <= j - v[i]\)
我们似乎可以写出
for(int i = 1 ; i <= n ; i ++ )
for(int j = v[i] ; j <= m ; j ++ )
{
f[j] = max(f[j],f[j - v[i]]+w[i])
}
乍一看好像是对的
其实
i
由 i - 1
决定
j
由 i - 1
时 j or j - v[i]
决定
j < j - 1
且j -->
是从小往大枚举j
,f[i - v[i]]
可能会在第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]);
return 0;
完全背包
有$ N $种物品和一个容量是 \(V\) 的背包,每种物品都有无限件可用。
第 \(i\) 种物品的体积是 \(v_i\),价值是 \(w_i\)。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
设DP方程\(f_{i,j}\)下每件物品选\(k\)件
状态转移方程为
\[\mathcal{ \huge f_{i,j} = f_{i-1,j-k \times v_i}+ k \times w_i}
\]
我们把方程展开
\[\large f_{i,j} = max(f_{i-1,j},f_{i-1,j-v}+w,f_{i-1,j-2v}+2w ,f_{i-1,j-3v}+3w \dots)
\]
\[\large f_{i,j -v} = max(f_{i-1,j-v},f_{i-1,j-2v}+w ,f_{i-1,j-3v}+2w \dots)
\]
\[\large f_{i,j -2v} = max(f_{i-1,j-2v} ,f_{i-1,j-3v}+w \dots)
\]
\[\large f_{i,j -3v} = max(f_{i-1,j-3v} \dots)
\]
\[\large max(f_{i-1,j-v}+w,f_{i-1,j-2v}+2w ,f_{i-1,j-3v}+3w \dots) = f_{i,j -v} +w
\]
\[\large f_{i,j} = max(f_{i-1,j},f_{i,j-v} + w)
\]
朴素
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n,m;
int w[N],v[N];
int f[N][N];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1; i <= n ; i ++ )scanf("%d%d",&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 - v[i] * k] + w[i] * k);
printf("%d",f[n][m]);
}
二维
#include <algorithm>
#include <cstdio>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N],f[N][N];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1 ;i <= n ;i ++ )scanf("%d%d",&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]);
}
printf("%d",f[n][m]);
}
一维
#include <algorithm>
#include <cstdio>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N],f[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1 ;i <= n ;i ++ )scanf("%d%d",&v[i],&w[i]);
for(int i = 1; i <= n ; i ++ )
for(int j = v[i] ; j <= m ; j ++)
{
f[j] = max(f[j],f[j - v[i]] + w[i]);
}
printf("%d",f[m]);
}
多重背包
有 \(N\) 种物品和一个容量是 \(V\) 的背包。
第 \(i\) 种物品最多有 \(s_i\) 件,每件体积是 \(v_i\),价值是 \(w_i\)。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 25000;
int n,m,f[N],w[N],v[N];
int main()
{
cin >> n >> m ;
int cnt = 0 ;
for(int i = 1 ; i <= n ; i ++ )
{
int a,b,c;
cin >> a >> b >> c;
int k = 1 ;
while(k <= c)
{
v[++cnt] = a * k ;
w[cnt] = b * k ;
c -= k;
k *= 2 ;
}
if(c > 0)
{
v[++cnt] = a * c ;
w[cnt] = b * c;
}
}
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] << endl;
}
分组背包
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N];
int v[N][N],w[N][N],s[N];
int n,m;
int main()
{
cin >> n >> m;
for(int i = 1 ;i <= n ; i++ )
{
cin >> s[i];
for(int j = 1 ;j <= s[i] ; j++ )
cin >> w[i][j] >> v[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(w[i][k] <= j)
f[j] = max(f[j],f[j - w[i][k]] + v[i][k]);
cout << f[m];
return 0;
}
“风雪越是呼啸,雪莲越是绽放”