[题解]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;  
}  
posted @ 2024-06-23 00:26  WBIKPS  阅读(34)  评论(0)    收藏  举报