[GDOI2014]Beyond

题目

传送门 to luogu

思路

先将题意简化一下。显然,前 L L L 次走过的格子,一定是原来的格子。也就是说, L L L 是合法的长度,当且仅当两个字符串的前 L L L 个字符构成的子串是循环同构的。

(题目本身有瑕疵:根据题意,应该还有一个条件为 L ∣ ( n − 1 ) L|(n-1) L(n1) ,否则没有合法的 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)jexB(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} Zalgorithm)。复杂度 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值