bzoj-3676 回文串

141 篇文章 0 订阅
88 篇文章 0 订阅

题意:

给出一个长度为n的字符串,求它的某个回文子串长度乘出现次数的最大值;

n<=300000;


题解:

据说这题用回文自动机回文树之类的东西有一些更优的解法?

然而回文自动机似乎是在这题之后被引入OI的23333;

所以还有一些听起来比较靠谱的解法。。

我们先考虑求出所有的回文子串,由于一些原因这些本质不同的回文子串最多有O(n)个;

利用manacher算法算出每个回文子串的位置与长度之后,考虑如何计算一个字符串的出现次数;

一个子串是后缀的前缀,那么两个相同的子串在进行后缀排序之后必定相邻;

有了这个性质,我们就可以借助后缀数组的height数组,来快速找出height数组上连续一段大于等于该字符串长度的个数;

找这个可以用RMQ预处理+二分左侧右侧长度实现 (不要吐槽我又求了一遍LCP);

这个个数乘上字符串长度就是答案了,注意极限情况可能爆int;

而且因为这题的时间复杂度是O(后缀数组nlogn+RMQ预处理nlogn+Manacher二分nlogn)所以隐藏常数巨大!

因此我们可以加一个小优化,在二分的时候,特判一下左右第一个是否满足大于等于字符串长度,如果不满足就不二分了;

这样就可以通过了,然而速度真是太慢啦2333;


代码:


#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 300010
#define S 26
using namespace std;
typedef long long ll;
int n,sa[N],rank[N],h[N],tr[N],hash[N];
int f[N];
int mi[N][20],LG[N];
ll ans;
char str[N];
inline bool cmp(int x,int y,int len)
{
	if(x+len>=n)	return 0;
	return rank[x]==rank[y]&&rank[x+len]==rank[y+len];
}
void getSA()
{
	register int i;
	int k,cnt;
	for(i=0;i<n;i++)		hash[str[i]-'a']++;
	for(i=0,cnt=-1;i<S;i++)	if(hash[i])	tr[i]=++cnt;
	for(i=1;i<S;i++)		hash[i]+=hash[i-1];
	for(i=0;i<n;i++)		rank[i]=tr[str[i]-'a'],sa[--hash[str[i]-'a']]=i;
	for(k=2;cnt<n-1;k<<=1)
	{
		memset(hash,0,sizeof(int)*n);
		for(i=0;i<n;i++)		hash[rank[i]]++;
		for(i=1;i<n;i++)		hash[i]+=hash[i-1];
		for(i=n-1;i>=0;i--)		if(sa[i]>=k>>1)	tr[sa[i]-(k>>1)]=--hash[rank[sa[i]-(k>>1)]];
		for(i=1;i<=k>>1;i++)	tr[n-i]=--hash[rank[n-i]];
		for(i=0;i<n;i++)		sa[tr[i]]=i;
		for(i=1,cnt=0;i<n;i++)
			tr[sa[i]]=cmp(sa[i-1],sa[i],k>>1)?cnt:++cnt;
		memcpy(rank,tr,sizeof(int)*n);
	}
	for(i=0;i<n;i++)
	{
		if(rank[i])
		{
			for(k=max(h[rank[i-1]]-1,1);k<n;k++)
				if(str[sa[rank[i]]+k-1]==str[sa[rank[i]-1]+k-1])
					h[rank[i]]=k;
				else	break;
			mi[rank[i]][0]=h[rank[i]];
		}
	}
}
inline int LCP(int x,int y)
{
	if(x==y)	return n-x;
	if(rank[x]>rank[y])
		swap(x,y);
	int k=LG[rank[y]-rank[x]]; 
	return min(mi[rank[x]+1][k],mi[rank[y]-(1<<k)+1][k]);
}
inline ll calc(int st,int len)
{
	int ret=1,l,r,mid;
	if(LCP(sa[rank[st]-1],st)>=len)
	{
		l=1,r=rank[st];
		while(l<=r)
		{
			mid=l+r>>1;
			if(LCP(sa[rank[st]-mid],st)>=len)
				l=mid+1;
			else
				r=mid-1;
		}
		ret+=r;
	}
	if(LCP(sa[rank[st]+1],st)>=len)
	{
		l=1,r=n-rank[st]-1;
		while(l<=r)
		{
			mid=l+r>>1;
			if(LCP(sa[rank[st]+mid],st)>=len)
				l=mid+1;
			else
				r=mid-1;
		}
		ret+=r;
	}
	return (ll)ret*len;
}
void manacher()
{
	int i,ma,id;
	for(i=0,ma=-1,id=0;i<n;i++)//ÆæÊý
	{
		f[i]=max(0,min(f[id+id-i],ma-i+1));
		while(i+f[i]<n&&i-f[i]>=0&&str[i+f[i]]==str[i-f[i]])
		{
			f[i]++;
			if(i+f[i]-1>ma)
			{
				ma=i+f[i]-1,id=i;
				ans=max(ans,calc(i-f[i]+1,f[i]+f[i]-1));
			}
		}
	}
	f[0]=0;
	for(i=0,ma=-1,id=0;i<n;i++)//żÊý
	{
		f[i]=max(0,min(f[id+id-i],ma-i+1));
		while(i+f[i]<n&&i-f[i]-1>=0&&str[i+f[i]]==str[i-f[i]-1])
		{
			f[i]++;
			if(i+f[i]>ma)
			{
				ma=i+f[i],id=i;
				ans=max(ans,calc(i-f[i],f[i]+f[i]));
			}
		}
	}
}
int main()
{
	int i,j,k;
	gets(str);
	n=strlen(str);
	getSA();
	LG[1]=0;
	for(i=2;i<=n;i++)
		LG[i]=LG[i>>1]+1;
	for(k=1;k<=19;k++)
		for(i=1;i<n-(1<<k-1);i++)
			mi[i][k]=min(mi[i][k-1],mi[i+(1<<k-1)][k-1]);
	manacher();
	printf("%lld\n",ans);
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值