P4141 消失之物
原题链接:P4141 消失之物 - 洛谷
一道好题,这题的难点在于如何处理不选 \(i\) 物品。
一种思想是我对原序列做正反两次 01 背包,通过 \(f_{i - 1,k}\) 和 \(g_{i + 1,j - k}\) 把 \(i\) 给跳过去,那么有 \(cnt(i,j)= \sum_{k = 0}^{j} f_{i - 1,k} \times g_{i + 1,j - k}\),显然,这样是时间复杂度是 \(\operatorname O(nm^2)\),我们无法接受。
正着求不好求,那我们尝试反着求。我们先算出 \(f_{n,m}\),然后设 \(g_{i,j}\) 为从 \(1\sim n\) 里面选,在不选择 \(i\) 的情况下体积恰好为 \(j\) 的方案数量。我们可以用 \(i\) 的选与不选来拆开 \(f_{n,j}\)。
可以发现如果我们选 \(i\) 的话,那么方案数量有 \(g_{i, j - w_i}\) 个(我们把 的体积去掉,在不选 的情况下就等于选 ),如果不选 的话,方案数量就有 \(g_{i,j}\) 个。那么就有 \(f_{n,m} = g_{i, j - w_i} + g_{i, j}\),那么就有 \(g_{i, j} = f_{n, j} - g_{i, j - w_i}\)(即 \(不选 i 的方案数 = 总方案数 - 选的i的方案数\)),特别的如果 \(j < w_i\) 的话,那么 \(g_{i,j} = f_{n,j}\)(因为压根装不了 \(i\))。这样我们就得到了 \(g_{i,j}\) 的转移方程式,易知初始化为 \(g_{i,0} = 1\)。因为只涉及两维,所以时间复杂度为 \(\operatorname O(nm)\),可以通过。
那么这题轻松加愉快的就结束了。可以看出,我们还是需要大胆地去设状态,创新地分类。通过已有量简化转移。或者说,找清本质,分析问题的来源,然后推导。这确实是一道好题。
代码为:
/*
大胆设,敢设,找齐不全,得到全面
*/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2010;
int f[N], g[N];
int v[N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> v[i];
f[0] = 1;
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= v[i]; j -- )
f[j] = (f[j] + f[j - v[i]]) % 10;
for (int i = 1; i <= n; i ++ )
{
memset(g, 0, sizeof g);
g[0] = 1;
for (int j = 1; j <= m; j ++ )
{
if (j >= v[i]) g[j] = ((f[j] - g[j - v[i]]) % 10 + 10) % 10;
else g[j] = f[j] % 10;
cout << g[j];
}
puts("");
}
return 0;
}