Manacher部分:
下标 i :0 是$ , 原字符串插入#字符变为 奇数长度,结尾位置添加@ 维持奇数字符个数
arr字符串: 经过处理的字符串 , eg -> fabbac “$ # f # a # b # b # a # c # @”
辅助数组p:p[i] 表示 arr字符串 在 i 位置的最长回文半径
两个关系:
- 最长回文串(是原串"fabbac"的最长回文串长度) = p[i] - 1;
- 以 i 为中心的 回文串( arr串 ) 起始位置(索引) = ( i - p[i] ) / 2;
知道这些,再看代码就能秒懂了。
板子:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e6 + 7;
const int Inf = 1e9 + 7;
int N , M;
char s[Maxn] , t[Maxn];
int Manacher() {
int lens = strlen(s);
if (lens < 2) {
return lens;
}
// 第一步:预处理,将原字符串转换为新字符串
int n = -1;
t[++n] = '$';
for (int i = 0 ; i < lens ; i++) {
t[++n] = '#' , t[++n] = s[i];
}
t[++n] = '#' , t[++n] = '@';
++n;
// 第二步:计算数组p、起始索引、最长回文半径
int p[n];
int id = 0, mx = 0;
int maxLength = -1;
int index = 0;
for (int j=1; j<n-1; j++) {
p[j] = mx > j ? min(p[2*id-j], mx-j) : 1;
// 向左右两边延伸,扩展右边界
while (t[j+p[j]] == t[j-p[j]]) {
p[j]++;
}
// 如果回文子串的右边界超过了mx,则需要更新mx和id的值
if (mx < p[j] + j) {
mx = p[j] + j;
id = j;
}
// 如果回文子串的长度大于maxLength,则更新maxLength和index的值
if (maxLength < p[j] - 1) {
maxLength = p[j] - 1;
index = j;
}
}
return maxLength;
}
int main()
{
}
回文树部分
套用AC自动机思想,建立两颗树
0树 和 1树 , 前者表示偶数回文串,后者表示奇数回文串
通过fail指针操作可以在两棵树中 来回跳跃
具体内容 就看代码解释,可以很好地理解
主要是要知道
- fail 指向当前now位置 前的最长回文后缀 的 回文后缀
- last在运行过程中会一直改变,所以 S[ 1 ~ i ] 的本质不同的回文串 是 cnt - 1 个
还有一些点需要了解
我们计算给出的S串的 所有回文子串(包括重复的)的数目,有两种方法
- 第一种是用count函数,但是计算的时候0树 和1树 的cnt被重复计算了,所以只算一个就行。
void count() { All = 0;//注意取模问题 for(int i = cnt ; i >= 0 ; --i){ rcnt[fail[i]] = (rcnt[fail[i]] + rcnt[i]) % Mod; if(i > 0) All = (All + rcnt[i]) % Mod; } }
- 在for(1 ~ lens)的过程中 把每次的 num[last] 加到一个sum中即可。思考一下,这个方法不仅是计算所有回文子串数目的方法,也是计算 已插入字符集(可以是1 ~ i 或 n ~ i) 的回文子串数目——包括重复部分(再强调一下)。
//sum[lens] 就是全部回文子串的数目 sum[0] = 0; for(int i = 1 ; i <= lens ; ++i){ add(S[i]); sum[i] = (sum[i-1] + (LL)num[last]) % Mod; } //_________________________________ //sum[1]也是全部回文子串的数目 sum[lens+1] = 0; for(int i = lens ; i >= 1 ; --i){ add(S[i]); sum[i] = (sum[i+1] + (LL)num[last]) % Mod; }
当这个模板处理字符串过长时可能会MLE,这时候就需要一些处理,或者直接考虑放弃此算法,改用manacher了,因为我们改用链表模拟时,如果要处理的字符串中字符范围是100,很可能会TLE,当然只有大小写字母是能满足的。
用下面第一个模板1 在2e6 * 26时就会MLE , 改用模板2 可过,当然速度稍慢一些,毕竟常数大了点。
模板1(数组模拟版)
const int Maxn = 5e5 + 7;
const int Inf = 1e9 + 7;
const int Over = 26;
int N , M;
struct Plt{
char str[Maxn];
int lens;
int nxt[Maxn][Over];
int fail[Maxn];
int cnt[Maxn];
int num[Maxn];
int len[Maxn];
int S[Maxn];
int last;
int n;
int p;
int newnode(int l) {
for (int i = 0 ; i < Over ; ++ i) nxt[p][i] = 0;
cnt[p] = 0;
num[p] = 0;
len[p] = l;
return p++;
}
void init() {
p = 0;
newnode(0);
newnode(-1);
last = 0;
n = 0;
S[n] = -1;
fail[0] = 1;
}
int get_fail(int x) {
while(S[n - len[x] - 1] != S[n]) x = fail[x];
return x;
}
void add(int c) {
c -= 'a';
S[++n] = c;
int cur = get_fail(last);
if (!nxt[cur][c]) {
int now = newnode(len[cur] + 2);
fail[now] = nxt[get_fail(fail[cur])][c];
nxt[cur][c] = now;
num[now] = num[fail[now]] + 1;
}
last = nxt[cur][c];
cnt[last]++;
}
void count() {
for (int i = p - 1 ; i > 0 ; --i){
cnt[fail[i]] += cnt[i];
}
}
void Buildtree(){
for(int i = 0 ; i < lens ; ++i){
add(str[i]);
}
}
void solve(){
lens = strlen(str);
init();
Buildtree();
count();
}
};
模板2(链表版)
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 2e6 + 7;
const int Inf = 1e9 + 7;
const int Over = 26;
const LL Mod = 51123987;
int N , M;
struct link //next的邻接表
{
int u[Maxn+5];int v[Maxn+5];
int next[Maxn+5];int head[Maxn+5];
int tot;
void clear()
{
memset(head,-1,sizeof(head));
tot=0;
}
void clear(int x){head[x]=-1;}
int get(int x,int y)
{
for(int i=head[x];i!=-1;i=next[i])
{
if(u[i]==y)
return v[i];
}
return 0;
}
void insert(int x,int y,int z)
{
u[tot]=y; v[tot]=z;
next[tot]=head[x];
head[x]=tot++;
}
};
struct plt{
char S[Maxn];
int ss[Maxn];
int n;
int fail[Maxn];
int len[Maxn];
int num[Maxn];
int cnt[Maxn];
int last;
int p;
link next;
int lens;
int new_node(int x) {
cnt[p]=0;
next.clear(p);
num[p]=0;
len[p]=x;
return p++;
}
void init() {
next.clear();
p=0;
new_node(0);
new_node(-1);
last=0;
n=0;
ss[0]=-1;
fail[0]=1;
}
void Input(){
scanf(" %s",S+1);
lens = strlen(S+1);
}
int get_fail(int x){
while(ss[n-len[x]-1] != ss[n]) x=fail[x];
return x;
}
void add(int x){
x-='a';
ss[++n]=x;
int cur=get_fail(last);
if(!(last=next.get(cur,x)))
{
int now=new_node(len[cur]+2);
fail[now]=next.get(get_fail(fail[cur]),x);
next.insert(cur,x,now);
num[now]=num[fail[now]]+1;
last=now;
}
cnt[last]++;
}
void Buildtree(){
for(int i = 1 ; i <= lens ; ++i){
add(S[i]);
}
}
void count() {
for(int i = p-1 ; i >= 0 ; --i){
cnt[fail[i]] += cnt[i];
}
}
void solve(){
Input();
init();
Buildtree();
count();
}
};
int main()
{
}
题目补充:
洛谷 4555
洛谷1659
(下面的https://vjudge.net/contest/321569 密码:rushb)
HYSBZ - 3676
URAL - 1960
UVALive - 7041
CodeForces - 17E
HDU - 3948
题解:
https://blog.youkuaiyun.com/castomere/article/details/100081064