洛谷 P3649 [APIO2014]回文串 / YbtOJ「字符串算法」第3章 后缀自动机 H. 回文匹配 题解--zhengjun

思路

蒟蒻不会 SAM,所以只好用 SA + Manacher + st 表 + set + 单调栈 + 双指针的笨重做法了。

首先处理出每个点为中心的最长回文串长度 \(odd_i\),以及以 \(i\)\(i+1\) 为中心左右对称的最长回文串长度 \(evn_i\),这一步可以用 Manacher 轻松搞定。

接着求出以 \(i\) 为左端点最长的回文串长度,就是一个区间修改,最后单点查询,可以用线段树或者在 st 表上面打标记之后下推到最底层也可以。

然后再对于原串跑一次 SA,先把出现次数为 \(1\) 的考虑掉,然后考虑出现次数大于 \(1\) 的情况:

就是枚举每个排名为 \(i(i>1)\) 的后缀,用单调栈求出 \(i\) 左右第一个小于 \(height_i\)\(l,r\),所以 \(\forall j\in[l+1,r-1]\),都有 \(height_j\ge height_i\),此时,与 \(sa_i\) 号后缀的公共前缀 \(\ge height_i\) 的个数就有 \(r-l\) 个,于是问题就转换成了:

求出每个左端点在 \(i\),右端点不超过 \(i+height_i-1\) 的最长的回文子串。

这样对于回文串长度分奇偶讨论,下面讨论奇数的,偶数的一样做:

当求左端点在 \(i\) 时的答案时,设 \(j=i+height_i-1\),那么就要找到一个最大的 \(k\) 满足:

\[i\le k\le \lfloor\frac{i+j}{2}\rfloor,odd_k\ge k-i+1 \]

转化一下,第二个条件变为 \(k-odd_k\le i-1\)

于是我们可以将所有的 \(k\) 按照 \(k-odd_k\) 升序排序,然后从小到大枚举 \(i\),每一次双指针把比 \(i\) 小的 \(k-odd_k\) 加入集合中,然后只要满足第一个条件即可,也就是在集合中查询区间 \([i,\lfloor\frac{i+j}{2}\rfloor]\) 中最大的数,一个 upper_bound 即可。

回文串长度为偶数时同理。

代码

#include<bits/stdc++.h>
using namespace std;typedef long long ll;const int N=3e5+10,K=log2(N)+2;set<int>s0,s1;char s[N],t[N<<1];
int n,m,rk[N],sa[N],old[N<<1],id[N],p[N],cnt[N],h[N],l[N<<1],r[N],f[K][N],lg[N],stk[N],top,odd[N],evn[N],x0=1,x1=1,cur[2][N];
void getsa(){
	m=128;for(int i=1;i<=n;i++)cnt[rk[i]=s[i]]++;for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i>=1;i--)sa[cnt[rk[i]]--]=i;for(int i=1;i<=m;i++)cnt[i]=0;for(int len=1,k=0;len==1||m^n;m=k,len<<=1){
		k=0;for(int i=n-len+1;i<=n;i++)p[++k]=i;for(int i=1;i<=n;i++)if(sa[i]>len)p[++k]=sa[i]-len;
		for(int i=1;i<=n;i++)cnt[id[i]=rk[p[i]]]++;for(int i=1;i<=m;i++)cnt[i]+=cnt[i-1];
		for(int i=n;i>=1;i--)sa[cnt[id[i]]--]=p[i];for(int i=1;i<=n;i++)old[i]=rk[i];for(int i=1;i<=m;i++)cnt[i]=0;
		k=0;for(int i=1;i<=n;i++)rk[sa[i]]=old[sa[i]]==old[sa[i-1]]&&old[sa[i]+len]==old[sa[i-1]+len]?k:++k;
	}for(int i=1,k=0;i<=n;i++){if(k)k--;while(max(sa[rk[i]-1],i)+k<=n&&s[i+k]==s[sa[rk[i]-1]+k])k++;h[rk[i]]=k;}
}
void update(int l,int r,int x){int k=lg[r-l+1];f[k][l]=max(f[k][l],x);f[k][r-(1<<k)+1]=max(f[k][r-(1<<k)+1],x);}//打标记
void query(){for(int i=K-1;i>=1;i--)for(int j=1;j+(1<<i)-1<=n;j++)//下推标记
	f[i-1][j]=max(f[i-1][j],f[i][j]),f[i-1][j+(1<<(i-1))]=max(f[i-1][j+(1<<(i-1))],f[i][j]);}
int get(int i,int j){
	int mx=0;while(x1<=n&&cur[1][x1]-odd[cur[1][x1]]<i)s1.insert(cur[1][x1++]);//双指针
	while(x0<n&&cur[0][x0]-evn[cur[0][x0]]<i)s0.insert(cur[0][x0++]);
	auto it=s1.upper_bound((i+j)>>1);if(it!=s1.begin()){it--;if(*it>=i)mx=(*it-i)<<1|1;}//算长度时小心细节
	it=s0.upper_bound((i+j-1)>>1);if(it!=s0.begin()){it--;if(*it>=i)mx=max(mx,(*it-i+1)<<1);}return mx;
}
int main(){
	scanf("%s",s+1);n=strlen(s+1);m=0;t[m++]='#';for(int i=1;i<=n;i++)t[m++]=s[i],t[m++]='#';for(int i=0,mx=-1,q=-1;i<m;i++){
		l[i]=i>mx?1:min(mx-i+1,l[(q<<1)-i]);while(i-l[i]>=0&&i+l[i]<m&&t[i-l[i]]==t[i+l[i]])l[i]++;if(i+l[i]>mx)mx=i+l[i]-1,q=i;//马拉车 Manacher
	}for(int i=1,j=1;i<m;i+=2,j++)odd[j]=l[i]>>1;for(int i=2,j=1;i<m-1;i+=2,j++)evn[j]=l[i]>>1;
	for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;for(int i=1,j=1;i<m;i+=2,j++)update(j-(l[i]>>1)+1,j,j<<1);
	for(int i=2,j=1;i<m-1;i+=2,j++)if(l[i]>1)update(j-(l[i]>>1)+1,j,j<<1|1);query();getsa();
	stk[0]=1;for(int i=2;i<=n;i++){while(top&&h[i]<=h[stk[top]])top--;l[i]=stk[top];stk[++top]=i;}//单调栈求出左右端点 l,r
	stk[top=0]=n+1;for(int i=n;i>=2;i--){while(top&&h[i]<=h[stk[top]])top--;r[i]=stk[top];stk[++top]=i;}
	ll ans=0;for(int i=1;i<=n;i++)ans=max(ans,f[0][sa[i]]-(sa[i]<<1)+1ll);for(int i=1;i<=n;i++)cur[0][i]=cur[1][i]=i;
	sort(cur[0]+1,cur[0]+n,[](int x,int y){return x-evn[x]<y-evn[y];});//按要求排序
	sort(cur[1]+1,cur[1]+1+n,[](int x,int y){return x-odd[x]<y-odd[y];});
	for(int i=1;i<=n;i++)if(rk[i]>1)ans=max(ans,ll(r[rk[i]]-l[rk[i]])*get(i,i+h[rk[i]]-1));cout<<ans;return 0;//从小到大枚举 i
}

有错误请指出,谢谢--zhengjun

posted @ 2022-06-11 15:38  A_zjzj  阅读(45)  评论(0编辑  收藏  举报