[题解]AT_abc321_f [ABC321F] #(subset sum = K) with Add and Erase
思路
可撤销背包板子。
首先问题是用当前所拥有的数的集合凑出 \(x\) 的方案数。
这个问题明显可以背包解决,即 \(dp_j \leftarrow dp_j + dp_{j - a_i}\)。
但是,此问题中物品有可能会被删除,即变为了一个动态的问题,如果直接暴力计算时间复杂度为 \(\Theta(qn^2)\)。
那么,我们转化一下思路,如果我们使每一次变化的数 \(x\) 都假令为 \(a\) 中的最后一个元素。因为 0-1 背包对于数的先后顺序无关,所以此假令不会影响答案。
然后,再想一下 0-1 背包的板子,我们已经假令 \(x\) 为 \(a\) 中最后一位元素,如果 \(a\) 的大小为 \(m\),那么我们在枚举 \(i\) 时,当 \(i\) 在 \([1,m)\) 范围内时,此时更新的 DP 数组与原 DP 数组的值不变。
因此,我们只需要考虑 \(x\) 带来的贡献,我们将两种操作分开考虑。
Part 1 添加操作
因为是在 0-1 背包里面取出最后一个元素,所以与 0-1 背包相同。
for (re int i = n;i >= x;i--) dp[i] = Add(dp[i],dp[i - x]);
Part 2 删除操作
因为是删除,所以枚举的顺序也不同。
设想如果按照正常 0-1 背包的顺序枚举,当此时删除 \(2\) 时,如果能删除 \(dp_9\) 就会被 \(dp_7\) 所影响,但如果 \(a\) 中只有一个 \(2\),显然 \(dp_9\) 无法取到。
那么当倒序循环时,\(dp_9\) 会在 \(dp_7\) 之后计算到。
for (re int i = x;i <= n;i++) dp[i] = Sub(dp[i],dp[i - x]);
Code
#include <bits/stdc++.h>
#define re register
#define int long long
using namespace std;
const int N = 5010,mod = 998244353;
int q,n;
int dp[N];
inline int read(){
int r = 0,w = 1;
char c = getchar();
while (c < '0' || c > '9'){
if (c == '-') w = -1;
c = getchar();
}
while (c >= '0' && c <= '9'){
r = (r << 3) + (r << 1) + (c ^ 48);
c = getchar();
}
return r * w;
}
inline int Add(int a,int b){
return (a + b) % mod;
}
inline int Sub(int a,int b){
return ((a - b) % mod + mod) % mod;
}
signed main(){
dp[0] = 1;
q = read();
n = read();
while (q--){
int x;
char op[10];
scanf("%s",op);
x = read();
if (op[0] == '+'){
for (re int i = n;i >= x;i--) dp[i] = Add(dp[i],dp[i - x]);
}
else{
for (re int i = x;i <= n;i++) dp[i] = Sub(dp[i],dp[i - x]);
}
printf("%lld\n",dp[n]);
}
return 0;
}

浙公网安备 33010602011771号