[题解]AT_abc321_f [ABC321F] #(subset sum = K) with Add and Erase
思路
可撤销背包板子。
首先问题是用当前所拥有的数的集合凑出 xx 的方案数。
这个问题明显可以背包解决,即 dpj←dpj+dpj−aidpj←dpj+dpj−ai。
但是,此问题中物品有可能会被删除,即变为了一个动态的问题,如果直接暴力计算时间复杂度为 Θ(qn2)Θ(qn2)。
那么,我们转化一下思路,如果我们使每一次变化的数 xx 都假令为 aa 中的最后一个元素。因为 0-1 背包对于数的先后顺序无关,所以此假令不会影响答案。
然后,再想一下 0-1 背包的板子,我们已经假令 xx 为 aa 中最后一位元素,如果 aa 的大小为 mm,那么我们在枚举 ii 时,当 ii 在 [1,m)[1,m) 范围内时,此时更新的 DP 数组与原 DP 数组的值不变。
因此,我们只需要考虑 xx 带来的贡献,我们将两种操作分开考虑。
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 背包的顺序枚举,当此时删除 22 时,如果能删除 dp9dp9 就会被 dp7dp7 所影响,但如果 aa 中只有一个 22,显然 dp9dp9 无法取到。
那么当倒序循环时,dp9dp9 会在 dp7dp7 之后计算到。
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;
}
作者:WaterSun
出处:https://www.cnblogs.com/WaterSun/p/18262943
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步