[动态规划 + 字符串]P9753 [CSP-S 2023] 消消乐 题解
听术曲做题浪费了好多时间qwq 一开始写了一个什么 manacher + 线段树的闹弹代码
后来发现答案其实就是由回文串拼成的字符串的数量。
因此暴力dp就行啦?
这篇文章主要还是讲一讲时间复杂度吧。
我们拿一个 \(lst_i\) 表示第 \(i\) 个字符前面第一个回文串,\(dp_i\) 表示以第 \(i\) 个字符为结尾的由回文串拼起来的字符串数量。
则 \(dp_i = dp_{lst_i - 1} + 1\),答案就是 \(\sum_{i = 1} ^{n} dp_i\)。
#include <bits/stdc++.h>
#define int long long
constexpr int N = 2e6 + 7;
using namespace std;
int ans , n , lst[N] , dp[N];
char s[N];
signed main() {
ios :: sync_with_stdio(0) , cin.tie(0) , cout.tie(0);
cin >> n;
for(register int i = 1; i <= n; ++i) {
cin >> s[i];
int j = i - 1;
while(s[j] != s[i] && j > 0) {
j = lst[j] - 1;
} // 暴力往前跳
if(s[i] == s[j]) { //找到一个一样的字符就构成了新的回文,可以转移了
lst[i] = j;
dp[i] = dp[j - 1] + 1;
}
ans += dp[i];
}
cout << ans;
return 0;
}
我不认为这个代码有什么难点。
主要还是复杂度。这玩意看着像 \(O(N^2)\) 的对罢,实际上是 \(O(26N)\)
为啥捏?你可以想象一下这个串的结构。你往前跳的时候就意味着两头的字符不同对罢,就像下面这个。
\[b + 回文 + a
\]
然后你从这个 \(b\) 的位置继续往前跳,就会出现一个由 \(b\) 结尾的回文串和以 \(a\) 前一个字符结尾的回文串拼出来的大回文串
\[c + 大回文 + a
\]
这就很奇妙了,最坏的情况就是每次这么跳完之后两端的字符都不同。然而你一共只有 26 个小写字符可选,因此这个循环最多跳 26 次。