【GDOI2014】beyond

Description

有两个长度为n的字符串s,st,找出两个串前i个可以循环同构的最大长度。

如abcdx,cdabx两个串,答案为4。s的第一到第二个字符于st的第三到第四的字符相等。

Solution

很容易想到在s中枚举一个分界点i左边的字符
然后,很容易想到求得一个exa[i]为st的s从i开始的字符串最长公共前缀的长度。求得一个exb[i]为s的st从i开始的字符串最长公共前缀的长度。
那么当枚举到分界点为i时,设exa[i+1]为l,那么st[1..l]与s[i+1..i+l]相等。
那么再在st中从1..l+1枚举一个j使得exb[j]+1>=i,那么就是已经循环同构了,那答案就是i+j-1。因为s[i+1..i+exa[i+1]]与st[1..exa[i+1]]相等,1<=j<=i+1,st[j+1..j+exb[j+1]]与s[1..exb[j+1]]相等,至少有s[1..i]=st[j..i+j-1],因为s[i+1..i+exa[i+1]]=st[1..j],exa[i+1]>j,所以至少有i+j-1
得到exa,exb自然用的是exkmp——>扩展kmp
O(n^2)
自然时间会超。
Optimization

使用并查集。
用f[i]存储一个exb[i+1]>=i节点,因为每次更新的是前缀的长度,当前前缀包含前面的前缀,所以可以向前递进,跟kmp的思想差不多。初始值自然是f[i]=i-1。
每次查询时,一直向上递进到一个exb[i+1]>=i的节点即可。
O(n);
Code

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int i,j,k,l,t,n,m,ans,p;
char s[2000005],st[2000005];
int nexta[2000005],exa[2000005],nextb[2000005],exb[2000005];
int f[2000005];
int gf(int x,int y){
if(!x) return 0;
if (exb[x+1]>=y) return x;else f[x]=gf(f[x],y);
}
int main(){
scanf("%d",&n);
scanf("%s",s+1);
scanf("%s",st+1);
nexta[1]=n;
fo(i,2,n){
p=k+nexta[k]-1;l=nexta[i-k+1];
if(l+i>p){
j=p-i+1;if(j<0)j=0;
while(i+j<=n&&s[j+1]==s[i+j])j++;
k=i;
nexta[i]=j;
}
else{
nexta[i]=l;
}
}
k=0;
fo(i,1,n){
p=k+exa[k]-1;l=nexta[i-k+1];
if(l+i>p){
j=p-i+1;if(j<0)j=0;
while(i+j<=n&&s[j+1]==st[i+j])j++;
k=i;
exa[i]=j;
}
else{
exa[i]=l;
}
}
nextb[1]=n;
k=0;
fo(i,2,n){
p=k+nextb[k]-1;l=nextb[i-k+1];
if(l+i>p){
j=p-i+1;if(j<0)j=0;
while(i+j<=n&&st[j+1]==st[i+j])j++;
k=i;
nextb[i]=j;
}
else{
nextb[i]=l;
}
}
k=0;
fo(i,1,n){
p=k+exb[k]-1;l=nextb[i-k+1];
if(l+i>p){
j=p-i+1;if(j<0)j=0;
while(i+j<=n&&st[j+1]==s[i+j])j++;
k=i;
exb[i]=j;
}
else{
exb[i]=l;
}
}
fo(i,1,n) f[i]=i-1;
fo(i,2,n-1){
l=gf(exa[i+1],i);
if(l) ans=max(ans,l+i);
}
printf("%d",ans);
}