[Luogu P3649] [APIO2014]回文串(后缀自动机)(或回文自动机)

[Luogu P3649] [APIO2014]回文串(后缀自动机)(或回文自动机)

题面

给出一个长度为\(n\)的字符串,求它的所有回文子串的出现次数乘以长度的最大值

\(1 \leq n \leq 3 \times 10^5\)

分析

SAM做法:

方法来自2015年国家集训队论文。吐槽:为什么网上的大部分题解都是SAM+Manacher啊,明明一个SAM就可以很简洁解决。

我们对原串构造后缀自动机,求出每个节点right(endpos)集合中的最大值,即最后一次出现的结束位置,记为\(maxpos\).然后把反串放到自动机上跑,如果当前的匹配串\([i,i+len-1]\)覆盖了当前节点的\(maxpos\),那么\([i,maxpos]\)是一个满足条件的回文子串.另外为了求它的出现次数,还需要求出right集合的元素个数。这些都可以通过自底向上遍历parent树求出。

PAM做法:

先建出回文自动机,计算一下每个节点代表的串出现了多少次。然后在fail树上从深到浅累加出现次数。(因为若S出现,那么串S的子串也出现),相当于加上后缀的贡献。

因为PAM里的每个节点都代表回文串,直接扫一遍求最大值即可。

代码

SAM:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define maxc 26
#define maxn 600000
using namespace std;
typedef long long ll;
struct SAM{
#define len(x) (t[x].len)
#define link(x) (t[x].link) 
	struct node{
		int ch[maxc];
		int link;
		int len;
		int maxpos;//right集合中的最大值 
		int cnt;//该位置子串的出现次数,即right集合大小,在parent树上可求出。 
	}t[maxn+5];
	const int root=1;
	int ptr=1;
	int last=root;
	void extend(char ch,int pos){
		int c=ch-'a';
		int p=last,cur=++ptr;
		len(cur)=len(p)+1;
		t[cur].maxpos=pos; 
		t[cur].cnt=1;
		//cur和cur在parent树上的祖先的right集合中都会多出元素pos
		//打一个标记,等建完之后再向上合并 
		while(p&&t[p].ch[c]==0){
			t[p].ch[c]=cur;
			p=link(p);
		}
		if(p==0) link(cur)=root;
		else{
			int q=t[p].ch[c];
			if(len(p)+1==len(q)) link(cur)=q;
			else{
				int clo=++ptr;
				t[clo]=t[q];
				t[clo].cnt=0;
				//复制clo的时候,一定要把clo的和right集合有关的标记清空 
				//因为复制clo的原因是len(p)+1<len(q),即状态q代表的串中,长度不超过len(p)+1的串会在当前串结尾出现,right集合会多出元素pos 
				//但是长度超过len(p)+1的串就不会被cur状态的right集合影响,所以拆完点记得清零 
				link(q)=link(cur)=clo;
				len(clo)=len(p)+1;
				while(p&&t[p].ch[c]==q){
					t[p].ch[c]=clo;
					p=t[p].link;
				}
			}
		}
		last=cur;
	}
	void insert(char *s){
		int len=strlen(s+1);
		for(int i=1;i<=len;i++) extend(s[i],i);
	}
	
	void topo_sort(){
		queue<int>q;
		static int in[maxn+5];
		for(int i=1;i<=ptr;i++) in[link(i)]++;//实际上是拓扑序的逆序
		for(int i=1;i<=ptr;i++) if(!in[i]) q.push(i); 
		while(!q.empty()){
			int x=q.front();
			q.pop();
			if(link(x)==0) continue;
			t[link(x)].maxpos=max(t[link(x)].maxpos,t[x].maxpos);
			t[link(x)].cnt+=t[x].cnt;
			in[link(x)]--;
			if(!in[link(x)]) q.push(link(x));
		}
	}
	ll solve(char *s){
		static bool vis[maxn+5]; 
		ll ans=0;
		int len=strlen(s+1); 
		int matl=0;//匹配长度 
		int x=root;
		insert(s);
		topo_sort();
		for(int i=len;i>=1;i--){
			int c=s[i]-'a';
			while(x&&!t[x].ch[c]){ 
				x=link(x);
				matl=len(x);
			}
			if(t[x].ch[c]){
				matl++;
				x=t[x].ch[c];
			}
			int tmp=matl;
			for(int y=x;y!=root&&!vis[y];y=link(y),tmp=len(y)){
				if(y!=x) vis[y]=1;
				if(t[y].maxpos>=i&&t[y].maxpos<=i+tmp-1) ans=max(ans,1ll*(t[y].maxpos-i+1)*t[y].cnt);
					//如果反串的匹配串[i,i+tmp-1]覆盖了maxpos,那[i,maxpos]就是一个回文串 
			}
		}	
		return ans;
	}
#undef len
#undef link
}T; 

char str[maxn+5];
int main(){
	scanf("%s",str+1);
	printf("%lld\n",T.solve(str));
}

PAM:

//https://www.luogu.com.cn/problem/P3649
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxc 26
#define maxn 300000
using namespace std;
typedef long long ll;
int n;
char s[maxn+5];
struct PAM{
	struct node{
		int fail;
		int ch[maxc];
		int cnt;//覆盖次数 
		int len;
	}t[maxn+5];
	int ptr;
	int last;
	void ini(){
		ptr=1;
		t[0].len=0;
		t[0].fail=1;
		t[1].len=-1;
		t[1].fail=0;
		last=0;
	}
	int get_fail(int x,int n){
		while(s[n-t[x].len-1]!=s[n]) x=t[x].fail;
		return x;
	}
	void insert(int c,int pos){
		int p=get_fail(last,pos);
		if(t[p].ch[c]==0){
			int cur=++ptr;
			t[cur].len=t[p].len+2;
			t[cur].fail=t[get_fail(t[p].fail,pos)].ch[c];
			t[p].ch[c]=cur;
		} 
		t[t[p].ch[c]].cnt++; 
		last=t[p].ch[c];
	}
	inline ll calc(){
		ll ans=0;
		for(int i=ptr;i>=1;i--)  t[t[i].fail].cnt+=t[i].cnt;//回文自动机的性质,似乎fail树中后代的编号一定更大 
		for(int i=1;i<=ptr;i++) ans=max(ans,1ll*t[i].cnt*t[i].len);
		return ans;
	}
}T;
int main(){
	scanf("%s",s+1);
	n=strlen(s+1);
	T.ini();
	for(int i=1;i<=n;i++) T.insert(s[i]-'a',i);
	printf("%lld\n",T.calc());
}

posted @ 2020-02-26 22:01  birchtree  阅读(219)  评论(0编辑  收藏  举报