CF2048H Kevin and Strange Operation
Part1:观察描述
- 对于任意次操作后的字符串,其每个位置都对应原序列一段区间的 \(\max\)。我们记当前第 \(i\) 个数来源于为 \([l_i,r_i]\)。
- 初始时 \(l=r=\{1,2,\dots,n\}\)。考虑每次操作前 \(p\) 个数,等价于删去 \([l_1,r_1],[l_2,r_2],\dots[l_p,r_p]\),替换成 \([l_1,r_2],[l_2,r_3],\dots,[l_{p-1},r_p]\)。
- 注意到这个操作等价于每次删去 \(r_1\),执行了 \(k\) 次后 \(r=\{k+1,k+2,\dots,n\}\)。 同时,操作后 \(l,r\) 会保持原来的顺序,即数组元素递增。
Part2:设计算法
-
考虑枚举右端点从右往左 \(\text{DP}\)。记 \(f_{i,j}\) 为,右端点为 \(i\),左端点为 \(j\) 的方案数。
-
每次为了防止算重,我们贪心的仅在最大的合法 \(j\) 更新我们的方案。
-
更具体的,我们分以下情况讨论:
-
\(s_i=1\)
- 下一位只能是 \(1\):\(f_{i,j}\to f_{i-1,j-1}\)
-
\(s_i=0\)
-
下一位是 \(0\):\(f_{i,j}\to f_{i-1,j-1}\)
-
下一位是 \(1\):\(f_{i,j}\to f_{i-1,pre_i}(j>pre_i)\)
其中 \(pre_i\) 表示 \(i\) 前面最近的 \(s_j=1\)。
-
-
-
这个 \(\text{DP}\) 本质上是区间平移,单点修改。
我们可以另第二个维度 \(j'=i-j\),这样 \(f_i\to f_{i-1}\) 的时候大部分位置就不会变了(事实上会变的只有表示 \(pre_i\) 的位置)。
因为 \(pre_i\) 有单调性,我们容易将过程优化到 \(O(n)\)。
Part3:代码实现
点击查看代码
#include <bits/stdc++.h>
#define FL(i, a, b) for (int i = (a); i <= (b); ++i)
#define FR(i, a, b) for (int i = (a); i >= (b); --i)
using namespace std;
const int N = 1e6 + 10;
const int MOD = 998244353;
int n, pre[N], f[N];
char s[N];
void AddTo(int &x, int y) {
x = (x + y >= MOD? x + y - MOD : x + y);
}
void Solve() {
scanf("%s", s + 1);
n = strlen(s + 1);
f[0] = 1;
FL(i, 1, n) {
if (s[i] == '1') {
pre[i] = i;
} else {
pre[i] = pre[i - 1];
}
}
int j = n, cnt = 0, sum = 1, ans = 0;
FR(i, n, 1) {
for (; j > pre[i]; --j) {
AddTo(cnt, f[i - j]);
}
if (pre[i]) {
if (s[i] == '0') {
AddTo(f[i - pre[i]], cnt);
AddTo(sum, cnt);
}
AddTo(cnt, MOD - f[(i - 1) - pre[i]]);
}
AddTo(ans, sum);
AddTo(sum, MOD - f[i - 1]), f[i - 1] = 0;
}
printf("%d\n", ans);
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
Solve();
}
return 0;
}