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\) 更新我们的方案。

  • 更具体的,我们分以下情况讨论:

    1. \(s_i=1\)

      • 下一位只能是 \(1\)\(f_{i,j}\to f_{i-1,j-1}\)
    2. \(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;
}
posted @ 2025-07-02 18:19  徐子洋  阅读(17)  评论(0)    收藏  举报