[題解]AT_abc321_f [ABC321F] #(subset sum = K) with Add and Erase

WaterSunHB發表於2024-06-23

思路

可撤銷揹包板子。

首先問題是用當前所擁有的數的集合湊出 \(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;  
}  

相關文章