HackerRank Special Substrings 回文树+后缀自动机+set

传送门

既然要求对每个前缀都求出答案,不难想到应该用回文树求出所有本质不同的回文子串。

然后考虑如何对这些回文子串的前缀进行去重。

结论:答案等于所有本质不同的回文子串长之和减去字典序相邻的回文子串的LCP长度之和。

这个结论其实不难理解。可以回忆后缀数组经典题目:求一个字符串本质不同的子串个数。道理是一样的。

然后就有思路了,从空串开始每次加一个字符,用一个set维护当前所有本质不同的回文子串(只存左右端点),如果产生了新的回文子串就扔进set里跟前驱后继xjb更新一下答案。

字典序比较用后缀数组会比较方便。然而我不会写后缀数组,那就后缀自动机求LCP硬上好了。注意由于这题只需要考虑回文子串,所以正着反着都是一样的,这么一来也就不必对反串建后缀自动机,直接对原串建个自动机然后求最长公共后缀就行了。

注意这题用LCP比较字典序的时候需要对LCP是否超出串长进行特判,细节虽然不多但比较容易忽视。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
const int maxn=300005;
namespace SAM{
	int root,last,cnt=0,val[maxn<<1]={0},par[maxn<<1]={0},go[maxn<<1][26]={0};
	vector<int>G[maxn<<1];
	int id[maxn<<1],tim=0,rnk[maxn],f[maxn][19],log_tbl[maxn];
	void initalize();
	void extend(int,int);
	void dfs(int);
	int LCP(int,int);
}
using SAM::LCP;
namespace PAM{
	int last,cnt,go[maxn][26],val[maxn],par[maxn];
	int extend(int);
}
struct A{
	int l,r;
	A(int l,int r):l(l),r(r){}
	bool operator<(const A &a)const;
};
char s[maxn];
int n;
int main(){
	scanf("%d",&n);
	scanf("%s",s+1);
	SAM::initalize();
	PAM::par[0]=PAM::cnt=1;
	PAM::val[1]=-1;
	set<A>DS;
	long long ans=0;
	for(int i=1;i<=n;i++){
		int l=PAM::extend(i);
		if(l){
			set<A>::iterator u=DS.lower_bound(A(l,i)),v=u;
			if(u==DS.end()){
				if(!DS.empty())u=DS.find(*DS.rbegin());
			}
			else{
				if(u!=DS.begin())u--;
				else u=DS.end();
			}
			if(u!=DS.end()&&v!=DS.end())
				ans+=min(min(u->r-u->l,v->r-v->l)+1,LCP(u->r,v->r));
			ans+=i-l+1;
			if(u!=DS.end())ans-=min(min(i-l,u->r-u->l)+1,LCP(i,u->r));
			if(v!=DS.end())ans-=min(min(i-l,v->r-v->l)+1,LCP(i,v->r));
			DS.insert(A(l,i));
		}
		printf("%lld\n",ans);
	}
	return 0;
}
namespace SAM{
	void initalize(){
		root=last=cnt=1;
		for(int i=1;i<=n;i++)extend(s[i]-'a',i);
		for(int i=2;i<=cnt;i++)G[par[i]].push_back(i);
		dfs(1);
		log_tbl[0]=-1;
		for(int i=1;i<=n;i++)log_tbl[i]=log_tbl[i>>1]+1;
		for(int j=1;(1<<j)<n;j++)
			for(int i=1;i+(1<<j)-1<n;i++)
				f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
	}
	void extend(int c,int v){
		int p=last,np=++cnt;
		val[np]=val[p]+1;
		while(p&&!go[p][c]){
			go[p][c]=np;
			p=par[p];
		}
		if(!p)par[np]=root;
		else{
			int q=go[p][c];
			if(val[q]==val[p]+1)par[np]=q;
			else{
				int nq=++cnt;
				val[nq]=val[p]+1;
				memcpy(go[nq],go[q],sizeof(go[q]));
				par[nq]=par[q];
				par[np]=par[q]=nq;
				while(p&&go[p][c]==q){
					go[p][c]=nq;
					p=par[p];
				}
			}
		}
		id[np]=v;
		last=np;
	}
	void dfs(int x){
		if(id[x]){
			if(tim)f[tim][0]=val[last];
			rnk[id[x]]=++tim;
			last=x;
		}
		for(int i=0;i<(int)G[x].size();i++)dfs(G[x][i]);
		last=par[x];
	}
	int LCP(int l,int r){
		if(l==r)return l;
		l=rnk[l];
		r=rnk[r];
		if(l>r)swap(l,r);
		r--;
		int k=log_tbl[r-l+1];
		return min(f[l][k],f[r-(1<<k)+1][k]);
	}
}
namespace PAM{
	int extend(int i){
		int p=last,c=s[i]-'a';
		while(s[i]!=s[i-val[p]-1])p=par[p];
		if(!go[p][c]){
			int q=++cnt,now=p;
			val[q]=val[p]+2;
			do p=par[p];while(s[i]!=s[i-val[p]-1]);
			par[q]=go[p][c];
			last=go[now][c]=q;
			return i-val[q]+1;
		}
		else last=go[p][c];
		return 0;
	}
}
bool A::operator<(const A &a)const{
	if(r==a.r)return false;
	int t=LCP(r,a.r);
	if(t>r-l+1&&t>a.r-a.l+1)return r-l+1>a.r-a.l+1;
	if(t>r-l+1)return true;
	if(t>a.r-a.l+1)return false;
	return s[r-t]<s[a.r-t];
}
posted @ 2018-06-28 21:26  AntiLeaf  阅读(314)  评论(0编辑  收藏  举报