回文

\(reverse\) 之后和原来相同的字符串

比如 \(asdffdsa\)\(asdfdsa\) 都是回文

判断一个串是否为回文可以扫一遍,可以使用 \(hash\)等等

常用的相关算法还有有 \(manacher\) 和回文自动机

manacher

核心思想在于维护最远的回文中心和回文半径,利用回文的对称性最大限度的利用已知信息,降低复杂度。

code
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 22000005;
char c[maxn], s[maxn];
int p[maxn];

int main(){
	scanf("%s",c + 1);
	int n = strlen(c + 1), cnt = 0;
	s[++cnt] = '~', s[++cnt] = '#';
	for(int i = 1; i <= n; ++i)s[++cnt] = c[i], s[++cnt] = '#';
	s[++cnt] = '!';
	int mr = 0, mid = 0, ans = 0;
	for(int i = 2; i < cnt; ++i){
		if(i <= mr)p[i] = min(p[mid * 2 - i], mr - i + 1);
		else p[i] = 1;
		while(s[i - p[i]] == s[i + p[i]]) ++p[i];
		if(i + p[i] > mr)mr = i + p[i] - 1, mid = i;
		ans = max(ans, p[i]);
	}
	printf("%d\n",ans - 1);
	return 0;
}

回文自动机/回文树/PAM

回文自动机的核心思想类似于 \(AC\) 自动机,是匹配,失配这样的模式,但是你们好像还没学 \(AC\) 自动机。。。

不同于 \(manacher\) 将回文信息放在回文中心处, \(PAM\) 的信息在树上,或者你也可以说他在末尾,通过不断匹配尝试拓展。

他的\(fail\) 指针是指向最长回文后缀

奇偶回文的处理也不同于 \(manacher\)

状态数等具体的证明请看oiwiki

这里只放一下他的复杂度证明

image

关于 \(fail\)

image

这有一个一步步图示\(abbaabba\)的,如果还是不太懂可以看看
https://blog.csdn.net/Clove_unique/article/details/53750322

code
#include<bits/stdc++.h>

using namespace std;

const int maxn = 5e5 + 55;
char s[maxn];
int ch[maxn][26], len[maxn], fail[maxn], dep[maxn], cnt = 1;

int main(){
	scanf("%s",s + 1);
	s[0] = '#';
	int n = strlen(s + 1);
	int las = 0, ans = 0;
	fail[0] = 1; len[1] = -1;
	for(int i = 1; i <= n; ++i){
		if(i != 1)s[i] = (s[i] - 97 + ans) % 26 + 97;
		while(s[i - len[las] - 1] != s[i])las = fail[las];
		if(!ch[las][s[i] - 'a']){
			len[++cnt] = len[las] + 2;
			int j = fail[las];
			while(s[i - len[j] - 1] != s[i])j = fail[j];
			fail[cnt] = ch[j][s[i] - 'a'];
			ch[las][s[i] - 'a'] = cnt;
			dep[cnt] = dep[fail[cnt]] + 1;
		}	
		las = ch[las][s[i] - 'a'];
		printf("%d ",ans = dep[las]);
	}
	return 0;
}

例题

简要题解

在匹配过程中把相等条件改为不等即可

简要题解(PAM)

考虑双倍回文本身也是回文,而他的\(W^RW\) 也是回文,也就是他在跳\(fail\)的过程中能跳到一个点,其长度为自己长度的一半且为偶数,在维护回文自动机的过程中多维护一个类似 \(fail\) 的指针,指向小于等于当前节点长度一半的最长回文后缀。

当恰好等于一半且为偶数时可以统计答案。

简要题解(马拉车)

考虑所有O(n)个本质不同的回文串(证明见 \(oi-wiki\)),对每个回文串都可以\(O(1)\)判断。具体实现中,只需要在\(manacher\)\(mx\)更新时,判断所有新出现的回文串的前一半是否为回文串即可。

(洛谷第一篇题解)

简要题解

这个题确实没看到马拉车怎么做

首先观察性质

发现你能翻转的话一定会用翻转($x + 1 \leq x \times 2 $)

答案一定是一个回文再暴力拼接构成

那现在需要构成每种回文的最小操作次数

我们的操作过程大体是暴力加,翻倍,暴力加,翻倍。。。

翻倍?还是用上面那个\(trans\)指针

image

简要题解

使用 \(PAM\) 考虑如何维护每个回文的出现次数

一个回文出现,他的所有回文后缀一定出现,走到每个节点加一最后在 \(fail\) 树上做子树和即可

简要题解

\(PAM\) 比较方便,记录每个点向前和向后的最长回文,然后你就会做了

马拉车也是类似,你在回文中心记录了长度,顺推一遍倒推一遍,每推一个位置就减去\(2\)

  • 萨鲁曼的半兽人

求最少将序列划分成几段,使得每一段都为回文,划分的段可以有交叉

简要题解

求回文可以hash + 二分,但是回文自动机显然更方便(而且回文长度在右端点)

用线段树维护DP数组,只需要在最长的回文范围内查询最小值 + 1更新当前位置答案

如果使用马拉车,也是类似的思路,但是要复杂一点,在左侧和前一个位置取 \(min\) 再加一,更新右侧区间

posted @ 2024-07-22 21:37  Chen_jr  阅读(72)  评论(0编辑  收藏  举报