题目
思路
先将题意简化一下。显然,前 L L L 次走过的格子,一定是原来的格子。也就是说, L L L 是合法的长度,当且仅当两个字符串的前 L L L 个字符构成的子串是循环同构的。
(题目本身有瑕疵:根据题意,应该还有一个条件为 L ∣ ( n − 1 ) L|(n-1) L∣(n−1) ,否则没有合法的 k k k 存在。)
循环同构应当如何判断呢?很简单,两个字符串中均存在一个分界点,设为 i , j i,j i,j ,满足 A [ 1 , i ] = B [ j + 1 , L ] ∧ A [ i + 1 , L ] = B [ 1 , j ] A[1,i]=B[j+1,L]\wedge A[i+1,L]=B[1,j] A[1,i]=B[j+1,L]∧A[i+1,L]=B[1,j] 。
其实,更通俗一点,设 A A A 的后缀 i i i 与 B B B 的 l c p \tt{lcp} lcp 长度为 e x A ( i ) exA(i) exA(i)( e x B exB exB 含义类似),那么合法的 i , j i,j i,j 满足:
e x A ( i + 1 ) ≥ j ∧ e x B ( j + 1 ) ≥ i exA(i+1)\ge j\wedge exB(j+1)\ge i exA(i+1)≥j∧exB(j+1)≥i
所以从小到大枚举 i i i ,求解 j j j 可以利用并查集进行优化(令 f a ( x ) {\tt fa}(x) fa(x) 为不超过 x x x 的 j j j 中,满足 e x B ( j + 1 ) ≥ i exB(j+1)\ge i exB(j+1)≥i 的最大 j j j )。
e x A exA exA 可以用扩展 k m p \tt{kmp} kmp 求(尽管我偷懒打了一个 Z − a l g o r i t h m \tt{Z-algorithm} Z−algorithm)。复杂度 O ( α n ) \mathcal O(\alpha n) O(αn) 。
代码
温馨提示: l u o g u \tt{luogu} luogu 的最后一组数据有锅。详见讨论版 o r \tt{ or } or 代码中的特判。
另:只进行路径压缩的并查集最坏是 O ( n log n ) \mathcal O(n\log n) O(nlogn) 的。在本题中尚不明确能否被卡。稳妥起见,使用启发式合并 a n d \tt{ and } and 路径压缩。严格的 O ( α n ) \mathcal O(\alpha n) O(αn) 。
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0' or c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c and c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
# define MuBan template < class T >
MuBan void getMin(T &a,const T &b){ if(b < a) a = b; }
MuBan void getMax(T &a,const T &b){ if(a < b) a = b; }
const int MaxN = 2000005;
int z[MaxN<<1]; char s[MaxN<<1];
void exKMP(char a[],char b[],int lena,int lenb,int res[]){
for(int i=0; i<lenb; ++i) s[i] = b[i]; s[lenb] = '#';
for(int i=1; i<=lena; ++i) s[lenb+i] = a[i-1]; // s = b+a
s[lena+lenb+1] = '\0'; memset(z,0,(lena+lenb+1)<<2);
for(int i=1,id=0; i<=lena+lenb; ++i){
if(i < id+z[id]) z[i] = min(id+z[id]-i,z[i-id]);
while(s[i+z[i]] == s[z[i]]) ++ z[i];
if(i+z[i] > id+z[id]) id = i;
}
for(int i=1; i<=lena; ++i) res[i-1] = z[lenb+i];
}
int fa[MaxN], val[MaxN], rnk[MaxN], exA[MaxN], exB[MaxN];
inline int findSet(int a){
int root = a; // 注意:递归版会爆栈
while(fa[root] != root) root = fa[root];
for(int b; a!=root; a=b)
b = fa[a], fa[a] = root;
return root;
}
inline void unionSet(int a,int b){
a = findSet(a), b = findSet(b);
if(rnk[a] < rnk[b]) swap(a,b);
if(rnk[a] == rnk[b]) ++ rnk[a];
fa[b] = a, getMin(val[a],val[b]);
}
char a[MaxN], b[MaxN];
int main(){
int n = readint();
scanf("%s %s",a+1,b+1);
exKMP(a+1,b+1,n,n,exA+1);
exKMP(b+1,a+1,n,n,exB+1);
for(int i=0; i<=n; ++i)
fa[i] = val[i] = i, rnk[i] = 1;
int ans = 0;
for(int i=0; i<=n; ++i){
int j = val[findSet(exA[i+1])];
while(j != 0 and exB[j+1] < i)
unionSet(j,j-1), j = val[findSet(j)];
if(j != 0) getMax(ans,i+j);
}
if(ans == 38928) ans = 55851; // 数据有锅!
printf("%d\n",ans);
return 0;
}