题解:P17337 【MX-X30-T3】宇宙分解
___Fengwenjie1___ · · 题解
从来没有见过这么一眼的 DP。
题意
P17337 【MX-X30-T3】宇宙分解
你有一个长度为
n 的01 序列a 和两种操作:
- 选择
a_i<a_{i+1} 并删去a_{i+1} 。- 选择
a_i<a_{i+1} 并交换这两个数。可以进行若干次这两种操作(可以不进行),求得到的序列数,对
998244353 取模,多测。设
s 为序列中1 的个数。1\le\small{\sum n}\le 10^5\quad 0\le\small{\sum s}\le 2000
思路
大概可以想象成把一堆
首先上来发现一个东西,最开头的那一段
然后发现,在固定最后剩下的
简单证明一下,挺显然的。
把选出来不删除的
设
设要求最后的
::::info[proof]
那么看看
由于每次都是减少,所以:
直接对比。我们设
显然有:
显然有:
所以
所以直接取
::::
然后,
把
解的个数,要求解为非负整数,
显然是把
看起来不好求,但是这道题允许了
因为你要求
稍微转换一下,设
咋转移?要求
答案就是所有
复杂度
如果你真的傻乎乎的一个一个给
这是区间加,直接差分优化掉一个
空间如果你直接开
代码
只能说没有任何难度。
static constexpr int MAXN = (int)(1e5) + 10;
int n, k0, k1, ans;
string s; bool flag;
int il[MAXN], dp[MAXN], id[MAXN];
void init(int n) {
std::fill(dp, dp + n + 6, 0);
k0 = 0, k1 = 0, ans = 0, flag = false;
}
void solve() {
cin >> n >> s, init(n);
s = ' ' + s, n = 0;
for (int i = 1; i < s.size(); i++) { // 用 flag 先把开头的 1 筛掉
if (!flag && s[i] == '1') continue;
il[++n] = s[i] - '0', flag = true;
il[n] == 1 ? (id[++k1] = k0) : k0++; // 记录 id
}
dp[k0] = 1, ans = 1; // 注意 k = 0 的时候有一种方案
for (int i = k1; i >= 1; i--) {
for (int j = 0; j <= k0; j++) {
int tmp = dp[j]; dp[j] = 0;
Add(dp[min(id[i], j)], tmp);
}
int res = 0;
for (int j = k0; j >= 0; j--)
Add(dp[j], dp[j + 1]), Add(res, dp[j]);
Add(ans, res);
}
cout << ans << '\n';
}
signed main() {
std::ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int n0; cin >> n0;
while (n0--) solve();
return 0;
}