思路
可撤銷揹包板子。
首先問題是用當前所擁有的數的集合湊出 \(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;
}