背包问题的逆推,P4141 消失之物
P4141 消失之物 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
有些背包问题需要我们对背包问题有较深刻的理解,背包问题中的递推属于这一类,详细掌握了背包问题的状态转移过程才能做出来。
下面便是一道涉及状态转移过程逆推的题目,这道题的逆推主要体现在解题过程而非在答案中体现:
题目描述
ftiasch 有 n 个物品, 体积分别是 1,2,…,w1,w2,…,wn。由于她的疏忽,第 i 个物品丢失了。
“要使用剩下的 n−1 物品装满容积为 x 的背包,有几种方法呢?”——这是经典的问题了。
她把答案记为 cnt(i,x) ,想要得到所有i∈[1,n], x∈[1,m] 的 cnt(i,x) 表格。
输入格式
第一行两个整数n,m,表示物品的数量和最大的容积。 第二行 n 个整数 1,2,…,w1,w2,…,wn,表示每个物品的体积。
输出格式
输出一个n×m 的矩阵,表示 cnt(i,x) 的末位数字。
输入输出样例
输入 #1复制
3 2 1 1 2
输出 #1复制
11 11 21
说明/提示
【数据范围】
对于 100%100% 的数据,1≤n,m≤2000。
【样例解释】
如果物品 3 丢失的话,只有一种方法装满容量是 2 的背包,即选择物品 1 和物品 2。
题目要求我们求出这个01背包问题中缺少任意一件物品后装满对应容积的背包的方案数
最暴力的方法显然是用三层循环扣去缺少的物品然后使用传统解法解决,但这显然会超时,
因此我们需要挖掘题目的性质,准确的说是挖掘状态转移过程的性质:
性质:状态为f[i][j]的状态由f[i-1][j](不选第i件物品)f[i-1][j-arr[i]](选第i件物品)转移过来。
如果我们不选第i-k件物品将 f[i-k-1][j-arr[i-k]](选第i-k件物品)过程删除。
除此之外,状态f[i-1][j](不选第i件物品)中也有选了第i-k件物品的状态,所以它也要将部分状态删除。
考虑开一个g[i] 表示当删掉某一个物品时,容量为i的方案数。这样,在背包容量递减的时候,我们需要不断的删掉v[i]转移过来的状态,并不断累减。
难点1
g[j]与f[i-1][j-arr[i]]并不等价,f[i-1][j-arr[i]]是前 i-1 中不选第 i 个的方案,而 g[j] 表示的是 n 个中不选第 i 个的方案,所以最后的答案并不等于
if (j >= arr[i]) {
ans = (f[n][j] - f[i-1][j - arr[i]] + 10) % 10;
}
else {
ans = f[n][j];
}
#include<iostream>
#include<set>
#include<vector>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<map>
#include<math.h>
using namespace std;
typedef long long LL;
const int N = 2e3 + 5;
int arr[N], n, m,f[N][N],g[N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &arr[i]);
}
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
f[i][0] = 1;
for (int j = 1; j <= m; j++) {
f[i][j] = f[i - 1][j];
if (j >= arr[i]) {
f[i][j] += f[i - 1][j - arr[i]];
f[i][j] %= 10;
}
}
}
/*printf("\n");
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
printf("%d ", f[i][j]);
}
printf("\n");
}*/
g[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (j >= arr[i]) {
g[j] = (f[n][j] - g[j - arr[i]] + 10) % 10;
}
else {
g[j] = f[n][j];
}
printf("%d", g[j]);
}
printf("\n");
}
return 0;
}