回文自动机简述

回文自动机简述

其实不算毒瘤,与AC自动机比较相似。

定义

回文自动机是有两棵树的森林,其处理了一个字符串中以某字符结尾的全部回文串。

其中一棵是长度为偶数的回文串集合,根为0,另一棵是长度为奇数的回文串集合,根为1。

对于串abbaabba的回文自动机如下

从网上随便找了张图

img

实现方法

回文串在回文自动机中实际上只存了一半。

如上图的7号节点,代表回文串baab,3号节点代表回文串b

理解自动机的关键就是理解(fail)指针,回文自动机的(fail)定义如下

对于节点i

回文自动机的fail,为i所代表的回文串的最长回文真后缀的那个点。

那么当我们匹配i+1失配时,我们可以跳到fail[i]然后比较fail所代表的回文串的前一个字符是否与i+1相等。

如此递归,直到跳到根,那么i+1的回文长度 len[i+1]=1否则len[i+1]=len[匹配到的fail]+1;

如何构建fail指针?

就如同AC自动机一样,我们考虑用上一个回文串的fail来构建这个新插入点的fail

我们先判断i这个字符是不是等于上一个串的fail串前一个,如果是就匹配成功。

否则继续沿着fail指针跳,边跳边判断就可以了。

(Problem List)

(1.)洛谷P1872回文串计数

(2.)[APIO2014]回文串

(Solution)

(1.)板子题。

/*
@Date    : 2019-07-15 16:33:37
@Author  : Adscn (adscn@qq.com)
@Link    : https://www.cnblogs.com/LLCSBlog
*/
#include<bits/stdc++.h>
using namespace std;
#define IL inline
#define RG register
#define gi getint()
#define gc getchar()
#define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
IL int getint()
{
	RG int xi=0;
	RG char ch=gc;
	bool f=0;
	while(ch<'0'||ch>'9')ch=='-'?f=1:f,ch=gc;
	while(ch>='0'&&ch<='9')xi=(xi<<1)+(xi<<3)+ch-48,ch=gc;
	return f?-xi:xi;
}
IL void pi(int k,char ch=0)
{
	if(k<0)k=-k,putchar('-');
	if(k>=10)pi(k/10,0);
	putchar(k%10+'0');
	if(ch)putchar(ch);
}
#define int long long
const int N=2e4+7;
char a[N];
long long l[N];
struct PAM{
	struct node
	{
		int c[26];
		int fail,len;
		node(){fail=len=0;}
	}t[N];
	int num[N];
	char S[N];
	int n,cnt;
	explicit PAM()
	{
		n=0;
		cnt=1;
		t[1].len=-1;
		t[0].fail=1;
	}
	inline int getfail(int x)
	{
		while(S[n]^S[n-t[x].len-1])x=t[x].fail;//这就是为什么t[1].len要等于-1
		return x;
	}
	inline int insert(int x)
	{
		S[++n]=(x-='a');
		int cur=getfail(last);
		if(!t[cur].c[x])
		{
			int nw=++cnt;
			t[nw].fail=t[getfail(t[cur].fail)].c[x];
			t[nw].len=t[cur].len+2;
			t[cur].c[x]=nw;
			num[nw]=num[t[nw].fail]+1;
		}
		last=t[cur].c[x];
		//++t[last].cnt;
		return num[last];
	}
	int last;
}x,y;
long long ans;
signed main(void)
{
	cin>>(a+1);
	int len=strlen(a+1);
	for(int i=1;i<=len;++i)l[i]=l[i-1]+x.insert(a[i]);
	for(int i=len;i>1;--i)ans+=1ll*l[i-1]*y.insert(a[i]);
	cout<<ans;
	return 0;
}

(2.)更加板子是smg

在fail树上累加cnt即可。

注意当我们将fail树上的儿子的贡献累加到父亲时,我们不必进行dfs

因为fail[x]<x,所以我们直接从n枚举到1就可以了

/*
@Date    : 2019-07-15 17:54:11
@Author  : Adscn (adscn@qq.com)
@Link    : https://www.cnblogs.com/LLCSBlog
*/
#include<bits/stdc++.h>
using namespace std;
#define IL inline
#define RG register
#define gi getint()
#define gc getchar()
#define File(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
IL int getint()
{
	RG int xi=0;
	RG char ch=gc;
	bool f=0;
	while(ch<'0'||ch>'9')ch=='-'?f=1:f,ch=gc;
	while(ch>='0'&&ch<='9')xi=(xi<<1)+(xi<<3)+ch-48,ch=gc;
	return f?-xi:xi;
}
IL void pi(int k,char ch=0)
{
	if(k<0)k=-k,putchar('-');
	if(k>=10)pi(k/10,0);
	putchar(k%10+'0');
	if(ch)putchar(ch);
}
const int N=3e5+7;
struct PAM{
	int n;
	int fail[N],cnt[N],c[N][26],len[N];
	int S[N];
	int last,tot;
	PAM()
	{
		tot=1,n=0;
		fail[0]=1,len[1]=-1;
	}
	inline int getfail(int x)
	{
		while(S[n]^S[n-len[x]-1])x=fail[x];
		return x;
	}
	inline void add(int x)
	{
		S[++n]=(x-='a');
		int cur=getfail(last);
		if(!c[cur][x])
		{
			int nw=++tot;
			fail[nw]=c[getfail(fail[cur])][x];
			len[nw]=len[cur]+2;
			c[cur][x]=nw;
		}
		++cnt[last=c[cur][x]];
	}
	void init()
	{
		static char s[N];
		cin>>(s+1);
		int Siz=strlen(s+1);
		for(int i=1;i<=Siz;++i)add(s[i]);
	}
}x;
int main(void)
{
	x.init();
	long long ans=0;
	for(int i=x.tot;i;--i)x.cnt[x.fail[i]]+=x.cnt[i],ans=max(ans,1ll*x.cnt[i]*x.len[i]);
	cout<<ans;
	return 0;
}

原文地址:https://www.cnblogs.com/LLCSBlog/p/11190127.html