非确定性有穷状态决策自动机练习题Vol.1 A.扭动的回文串
非确定性有穷状态决策自动机练习题Vol.1 A.扭动的回文串
题目描述
\(JYY\)有两个长度均为\(N\)的字符串\(A\)和\(B\)。
一个“扭动字符串\(S(i,j,k)\)由\(A\)中的第\(i\)个字符到第\(j\)个字符组成的子串
与B中的第\(j\)个字符到第\(k\)个字符组成的子串拼接而成。
比如,若\(A\)=’XYZ’,\(B\)=’UVW’,则扭动字符串\(S(1,2,3)\)=’XYVW’。
\(JYY\)定义一个“扭动的回文串”为如下情况中的一个:
1.\(A\)中的一个回文串;
2.\(B\)中的一个回文串;
3.或者某一个回文的扭动字符串\(S(i,j,k)\)
现在\(JYY\)希望找出最长的扭动回文串。
输入格式
第一行包含一个正整数\(N\)。
第二行包含一个长度为\(N\)的由大写字母组成的字符串A。
第三行包含一个长度为\(N\)的由大写字母组成的字符串B。
输出格式
输出的第一行一个整数,表示最长的扭动回文串。
样例
样例输入
5
ABCDE
BAECB
样例输出
5
样例解释
最佳方案中的扭动回文串如下所示(不在回文串中的字符用.表示):
.BC..
..ECB
数据范围与提示
对于\(10\%\)的数据:\(N≤100\);
对于\(30\%\)的数据:\(N≤1000\);
对于\(50\%\)的数据:\(N≤10000\);
对于\(100\%\)的数据:\(1≤N≤10^5\)
分析
对于只在 \(A\) 中的回文串和只在 \(B\) 中的回文串,我们直接拿马拉车 \(O(n)\) 解决即可
比较难处理的是扭动回文串的情况
显然,对于每一个回文串,都有一个回文中心(在马拉车算法前,我们已经把所有的偶回文串填充成了奇回文串)
我们可以把 \(A\) 串和 \(B\) 串中的每一个字符都当作回文中心计算贡献
在马拉车算法后,我们已经求出了以 \(i\) 作为回文中心的最长回文半径 \(f[i]\)
我们只需要在原来的基础上继续向外扩展即可
对于 \(A\) 串中的字符,我们在原串上向左扩展,在 \(B\) 串上向右扩展
对于 \(B\) 串中的字符,我们在原串上向右扩展,在 \(A\) 串上向左扩展
直接暴力去扫肯定会 \(T\) ,我们可以用二分+ \(Hash\) 将扩展的复杂度降为 \(\log(n)\)
对于 \(A\) 我们正着记录一遍哈希值,对于 \(B\) 我们倒着记录一遍哈希值即可
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1e6+5;
typedef unsigned long long ll;
const ll bas=233;
char a[maxn],b[maxn],ksa[maxn],ksb[maxn];
int fa[maxn],fb[maxn],n;
ll hasha[maxn],hashb[maxn],mi[maxn];
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int js=0;
ll get_hasha(int l,int r){
return hasha[r]-hasha[l-1]*mi[r-l+1];
}
ll get_hashb(int l,int r){
return hashb[l]-hashb[r+1]*mi[r-l+1];
}
//取哈希值
int solvea(int id){
int l=id-fa[id]+1,r=id+fa[id]-1;
int nl=0,nr=min(l-1,n-r),nmids;
while(nl<=nr){
nmids=(nl+nr)>>1;
if(get_hasha(l-nmids,l)==get_hashb(r-2,r+nmids-2)) nl=nmids+1;
else nr=nmids-1;
}
return fa[id]-1+nr;
}
int solveb(int id){
int l=id-fb[id]+1,r=id+fb[id]-1;
int nl=0,nr=min(l-1,n-r),nmids;
while(nl<=nr){
nmids=(nl+nr)>>1;
if(get_hasha(l-nmids+2,l+2)==get_hashb(r,r+nmids)) nl=nmids+1;
else nr=nmids-1;
}
return fb[id]-1+nr;
}
//对于两个串分别二分
int main(){
n=read();
scanf("%s%s",ksa+1,ksb+1);
n=n*2+1;
a[0]='$',b[0]='$';
for(int i=1;i<=n;i++){
if(i&1) a[i]='#';
else a[i]=ksa[i/2];
}
for(int i=1;i<=n;i++){
if(i&1) b[i]='#';
else b[i]=ksb[i/2];
}
mi[0]=1;
for(int i=1;i<=n;i++){
mi[i]=mi[i-1]*bas;
hasha[i]=hasha[i-1]*bas+a[i];
}
for(int i=n;i>=1;i--){
hashb[i]=hashb[i+1]*bas+b[i];
}
//预处理哈希值
int mids=0,r=0,ans=0;
for(int i=1;i<=n;i++){
if(i<=r) fa[i]=min(fa[mids*2-i],r-i+1);
while(a[i+fa[i]]==a[i-fa[i]]) fa[i]++;
if(i+fa[i]-1>r){
r=i+fa[i]-1,mids=i;
ans=max(ans,fa[i]-1);
}
}
mids=0,r=0;
for(int i=1;i<=n;i++){
if(i<=r) fb[i]=min(fb[mids*2-i],r-i+1);
while(b[i+fb[i]]==b[i-fb[i]]) fb[i]++;
if(i+fb[i]-1>r){
r=i+fb[i]-1,mids=i;
ans=max(ans,fb[i]-1);
}
}
//马拉车
for(int i=1;i<=n;i++){
ans=max(ans,solvea(i));
ans=max(ans,solveb(i));
}
printf("%d\n",ans);
return 0;
}