P9753 [CSP-S 2023] 消消乐 题解

题意

  • 类似于一般的消消乐,连着的两个相同的字母可以被消去,消去后字符串重新拼在一起。求能被完全消去的子串数量。

分析

  • 先想想什么样的字符串可以被全部消去。看完题目,有没有一种括号序列的感觉?
  • \(c\) 为任意小写字母,假设字符串 \(A\)\(B\) 都能被完全消去,那么 \(c+A+c\) 以及 \(A+B\) 这两种串都是可被完全消去的。
  • 那我们该怎么统计有哪些串可被完全消去呢?
  • 先不管那些长长的、像 \(A+B+\cdots\) 这样被拼在一起的串,我们先找出来最短的那些串,然后利用 DP 统计即可。

推导

1. 求出最短的可消去串

  • 定义 \(lst_i\) 表示以下标 \(i\) 为结尾的最短串的区间为 \(\left[lst_i, i\right]\)。特别的,不存在这样的区间时 \(lst_i=0\)
  • 对于 \(c+c\),也即 \(str_i = str_{i-1}\) 这种情况,\(lst_i=i-1\)
  • 对于 \(c+A+c\) 的情况,我们设 \(l=i-1\),while 循环判断 \(str_l\) 是否等于 \(str_i\),不等于的话就令 \(l=lst_l-1\)。可以结合下图来理解。
  • 图中每一个方框都是一个 \(\left[lst_i,i\right]\) 的区间,\(l\) 按照青色箭头方向转移。当 \(l\) 处在最左侧的 \(x\) 的位置时找到最小区间,赋值 \(lst_i=l\)

跳lst找c+A+c

2. 求出可消去串数量

  • 定义 \(f_i\) 表示以下标 \(i\) 为结尾的可消去串数量。那么当 \(lst_i\neq0\) 时就有转移

\[\large{f_i=f_{lst_i-1}+1} \]

  • 再举一个例子。如图,每一个方框都表示一个区间 \(\left[lst_i,i\right]\)。在求 \(f_9\) 时,区间 \(\left[8,9\right]\) 是一个新的可消去串,而且 \(f_7\) 所对应的每一个可消去串后面接上一个 \(\left[8,9\right]\) 也仍然是一个可消去串。
  • 于是就得到了式子 \(f_9=f_7+1\)\(f_7\) 代表的是 \(f_7\) 所对应的每一个可消去串后面接上一个 \(\left[8,9\right]\) 组成的串,\(+1\) 表示增加 \(\left[8,9\right]\) 这个串。

求解f_i的例子

3. 答案

  • 很显然,最终答案把所有的 \(f_i\) 加起来就好了。

考场代码

#include <bits/stdc++.h>
#define int long long
#define N 2000005
using namespace std;
int n, a[N], lst[N], f[N], ans;

signed main() {
//	freopen("game.in", "r", stdin);
//	freopen("game.out", "w", stdout);
	scanf("%lld", &n);
	char ch = n = 0;
	while (ch < 'a' || ch > 'z') ch = getchar();
	while (ch >= 'a' && ch <= 'z') {
		a[++n] = ch - 'a';
		ch = getchar();
	}
	for (int i = 2; i <= n; i++) {
		int l = i - 1;
		while (l > 0 && a[i] != a[l]) { //跳lst求解,说实话有点像KMP
			l = lst[l] - 1;
		}
		if (l == -1) l++;
		lst[i] = l;
	}
	for (int i = 2; i <= n; i++) {
		if (lst[i]) { //存在可消去串的就转移
			f[i] += f[lst[i] - 1] + 1;
		}
	}
	for (int i = 1; i <= n; i++) { //统计答案
		ans += f[i];
	}
	printf("%lld", ans);
	return 0;
}
/*
The moon is shining. Thank you, moon.
*/

  • 感谢观看~
posted @ 2024-02-27 19:44  wswwhcs  阅读(61)  评论(0编辑  收藏  举报