集合转化: $f[i][j]$ 表示考虑前$i$个物品,体积恰好
是$j$的方案数
求方案数
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 10010;
int n, m;
int f[N];
int main()
{
cin >> n >> m;
f[0] = 1;
// 什么数都不选是一种方案
while (n --)
{
int x;
cin >> x;
for (int i = m; i >= x; i --)
f[i] += f[i - x];
}
cout << f[m];
return 0;
}
完全背包: 买书
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int v[4] = {10, 20, 50, 100};
int f[N];
int main()
{
cin >> n;
f[0] = 1;
for (int i = 0; i < 4; i ++)
for (int j = v[i]; j <= n; j ++)
f[j] += f[j - v[i]];
cout << f[n];
return 0;
}
思路:
$f[j]$表示体积恰好为$j$时的最大价值,每一步转移的时候记录一下方案数即可
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n, m;
int f[N], g[N];
int main()
{
cin >> n >> m;
// 因为f[i]表示的是体积恰好为i时的最大价值,所以要初始化为负无穷
memset(f, -0x3f, sizeof f);
f[0] = 0;
g[0] = 1; // 方案数
for (int i = 1; i <= n; i ++)
{
int v, w;
cin >> v >> w;
for (int j = m; j >= v; j --)
{
int maxv = max(f[j], f[j - v] + w);
int s = 0;
if (maxv == f[j]) s = g[j]; // 如果可以由f[j]转移而来
if (maxv == f[j - v] + w) s = (s + g[j - v]) % mod; // 如果可以由f[j - v] + w转移而来
f[j] = maxv, g[j] = s;
}
}
// 因为集合表示的是恰好体积为i时的最大价值,所以f[m]并不一定是最大值,需要全部遍历一遍
int res = 0;
for (int i = 1; i <= m; i ++)
if (f[i] > f[res])
res = i;
int s = 0;
for (int i = 0; i <= m; i ++) // 这里要从0开始是因为如果一个物品都不能装的话,什么不装也是一种方案
if (f[i] == f[res])
s = (s + g[i]) % mod;
cout << s;
return 0;
}
求方案
模型:
1. 不能用一维优化.
2. 求方案时从后往前看当前状态是由选 $or$ 不选前一个物品的转移过来的
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 16;
int n, m;
int f[N][N], way[N];
int w[N][N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
cin >> w[i][j];
for (int i = 1; i <= n; i ++)
for (int j = m; j >= 0; j --)
for (int k = 0; k <= j; k ++)
f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
cout << f[n][m] << endl;
int j = m;
for (int i = n; i >= 1; i --)
for (int k = 0; k <= j; k ++)
if (f[i][j] == f[i - 1][j - k] + w[i][k]) // 如果当前状态是前一个状态转移过来的
{
j -= k;
way[i] = k;
break;
}
for (int i = 1; i <= n; i ++)
printf ("%d %d\n", i, way[i]);
return 0;
}
思路:
因为这题是要输出字典序最小的方案,则我们从后往前遍历物品做$01$背包
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
int f[N][N];
int v[N], w[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> v[i] >> w[i];
for (int i = n; i >= 1; i --)
for (int j = 0; j <= m; j ++)
{
// 如果两重循环的话, 则先要让第i维由上一维转移过来
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]);
}
int j = m;
for (int i = 1; i <= n; i ++)
// 第一个条件易漏: j只有大于v[i], 这个物品才能被选
if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i])
{
j -= v[i];
printf ("%d ", i);
}
return 0;
}
%%%