[动态规划 + 字符串]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 次。

posted @ 2025-04-17 22:38  一Kx一  阅读(24)  评论(0)    收藏  举报