题目链接
题目描述
小 L 现在在玩一个低配版本的消消乐,该版本的游戏是一维的,一次也只能消除两个相邻的元素。
现在,他有一个长度为 n n n 且仅由小写字母构成的字符串。我们称一个字符串是可消除的,当且仅当可以对这个字符串进行若干次操作,使之成为一个空字符串。
其中每次操作可以从字符串中删除两个相邻的相同字符,操作后剩余字符串会拼接在一起。
小 L 想知道,这个字符串的所有非空连续子串中,有多少个是可消除的。
输入格式
输入的第一行包含一个正整数 n n n,表示字符串的长度。
输入的第二行包含一个长度为 n n n 且仅由小写字母构成的字符串,表示题目中询问的字符串。
输出格式
输出一行包含一个整数,表示题目询问的答案。
输入输出样例 #1
输入 #1
8
accabccb
输出 #1
5
说明/提示
【样例 1 解释】
一共有
5
5
5 个可消除的连续子串,分别是 cc、acca、cc、bccb、accabccb。
【数据范围】
对于所有测试数据有: 1 ≤ n ≤ 2 × 1 0 6 1 \le n \le 2 \times 10^6 1≤n≤2×106,且询问的字符串仅由小写字母构成。
| 测试点 | n ≤ n\leq n≤ | 特殊性质 |
|---|---|---|
| 1 ∼ 5 1\sim 5 1∼5 | 10 10 10 | 无 |
| 6 ∼ 7 6\sim 7 6∼7 | 800 800 800 | 无 |
| 8 ∼ 10 8\sim 10 8∼10 | 8000 8000 8000 | 无 |
| 11 ∼ 12 11\sim 12 11∼12 | 2 × 1 0 5 2\times 10^5 2×105 | A |
| 13 ∼ 14 13\sim 14 13∼14 | 2 × 1 0 5 2\times 10^5 2×105 | B |
| 15 ∼ 17 15\sim 17 15∼17 | 2 × 1 0 5 2\times 10^5 2×105 | 无 |
| 18 ∼ 20 18\sim 20 18∼20 | 2 × 1 0 6 2\times 10^6 2×106 | 无 |
特殊性质 A:字符串中的每个字符独立等概率地从字符集中选择。
特殊性质 B:字符串仅由 a 和 b 构成。
算法思想(动态规划)
状态表示
f[i]表示字符串
s
s
s中以i位置作为结尾的可消除的子串数量,答案即为
∑
i
=
1
n
f
[
i
]
\sum_{i=1}^nf[i]
∑i=1nf[i]。
状态计算
如果存在位置j(
j
<
i
j<i
j<i),使得子串
s
[
j
,
i
]
s[j,i]
s[j,i]为可消除子串,如下图所示:

那么f[i] = f[j - 1] + 1。注意,这里的j是所有满足条件的位置中的最大值。
为了避免计算f[i]时枚举j,不妨将每次得到的j保存到g[i]中。在计算f[i]时:
- 首先令
j = i - 1 - 如果
s[j] != s[i],将j = g[j] - 1,即跳过以j结尾的合法子串,直到找到可消除的位置j,或者跳过整个字符串。跳转过程如下图所示:
- 如果找到可消除的位置j,令g[i] = j,计算f[i] = f[j - 1] + 1
时间复杂度
因为外层 for 循环执行
n
n
n 次,而 while 循环的总执行次数最多为
n
n
n 次,所以平均下来,每次外层循环中 while 循环的执行次数为常数次。因此时间复杂度为
O
(
n
)
O(n)
O(n)。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 5;
char s[N], stk[N];
int f[N], g[N];
int main()
{
int n;
long long ans = 0;
cin >> n >> s + 1;
for(int i = 1; i <= n; i ++)
{
int j = i - 1;
while(j >= 1 && s[j] != s[i]) j = g[j] - 1;
if(j >= 1)
{
g[i] = j;
f[i] = f[j - 1] + 1;
}
ans += f[i];
}
cout << ans << endl;
return 0;
}
1391

被折叠的 条评论
为什么被折叠?



