【牛客网】白兔的字符串(扩展kmp+hash+最小表示法)

【牛客网】15253 白兔的字符串

比较两个字符串是否循环同构从比较两个最小表示是否相同开始

我们把T转成循环同构,这不影响答案

然后我们要假定T真的是\(S\)长度为\(len(T)\)区间的一个最小表示,对于这个区间设为\([l,r]\),我们设另一个在\(S\)上的指针为\(p\),如果\(p\)匹配到了区间的末尾,那可以认为\(p\)开头的区间的一个循环同构表示法“可能是最小的”(任意一个后缀\([p',r]\)不会大于\([p,r]\)长度相等的前缀部分,因为\([p,r]\)匹配了\(T\)的一个前缀,\(T\)转化成了最小表示),这个时候只要用\([l,p - 1]\)和\(T\)的相应长度的后缀比较一下是否相等即可

这里考虑以下为什么当\([p,r]\)可以匹配前缀,存在\(p' > p\),\([p',r]\)也可以匹配前缀,为什么不会出现错误

因为我们假定了这个区间是循环同构\(T\)的,而只要它等于T,则\(p\)必然是区间循环同构的开头,而\(p'\)也可能是循环同构的开头,所以找谁不重要

而不等于\(T\),这个性质也没有什么作用,\(p\)是不是最小表示法的开头都无所谓

如果发现\(p\)开始的循环表示是比\(T\)严格大的一个串,类似与最小表示法的算法这一整段区间都可以跳过

如果是比\(T\)严格小呢?这个区间肯定是废了(不可能统计进答案了),但是为了编码的统一性,让\(p\)继续右移找到后缀匹配\(T\)的前缀可以保证操作的复杂度正确。为了复杂度正确,如果匹配了\(k\)长度的串,让\(p\)直接跳到\(p + k\)似乎不错,复杂度对了,但是这显然有问题

那么这既然是\([p,p + k - 1]\)是\(T\)的一段前缀,扩展kmp正好可以处理所有位置开始的后缀和前缀匹配长度,所以先对T跑一遍扩展kmp,然后对\([p,p + k - 1]\)的每一个位置判断能否满足\([p',p + k - 1]\)也匹配\(T\)的开头,让过程继续下去

如果\(p = l\),下一个区间是\([l + 1,r + 1]\),需要用类似的操作右移一下\(p\)

比较两个字符串是否相同可以哈希,防止冲突可以来双哈希

#include <bits/stdc++.h>
#define fi first
#define se second
#define mo 99994711
//#define ivorysi
#define enter putchar('\n')
#define space putchar(' ')
#define pii pair<int,int>
typedef long long int64;
using namespace std;

template<class T>
void read(T &res) {
	res = 0;T f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9') {
		res = res * 10 + c - '0';
		c = getchar();
	}
	res *= f;
}
template<class T>
void out(T x) {
	if(x < 0) {x = -x;putchar('-');}
	if(x >= 10) out(x / 10);
	putchar('0' + x % 10);
}
int mod[2] = {99994711,99977797};
int ba[2] = {91,47};
int e[2][2000006];
struct Hash {
	int hsh[2][2000006];

	void Init(char *t,int lenT) {
		for(int i = 1 ; i <= lenT ; ++i) {
			for(int j = 0 ; j <= 1 ; ++j) {
				hsh[j][i] = 1LL * e[j][i] * (t[i] - 'a' + 1) % mod[j];
				hsh[j][i] = hsh[j][i] + hsh[j][i - 1];
				if(hsh[j][i] >= mod[j]) hsh[j][i] -= mod[j];
			}
		}
	}

	pair<int,int> Query(int l,int r) {
		int res[2] = {0,0};
		for(int j = 0 ; j <= 1; ++j) {
			res[j] = hsh[j][r] - hsh[j][l - 1] + mod[j];
			if(res[j] >= mod[j]) res[j] -= mod[j];
			res[j] = 1LL * res[j] * e[j][1000000 - l] % mod[j];
		}
		return make_pair(res[0],res[1]);
	}
}hsh[2];
char T[2000006],S[2000006];
int N,lenT,lenS,exd[2000005];
void cycle(char *t,int st,int len) {
	static char tmp[1000006];
	int cnt = 0;
	for(int i = st ; i <= len ; ++i) tmp[++cnt] = t[i];
	for(int i = 1 ; i < st ; ++i) tmp[++cnt] = t[i];
	for(int i = 1 ; i <= len ; ++i) t[i] = tmp[i];
}
bool check(int l1,int r1,int l2,int r2) {
	if(r1 < l1) return true;
	pair<int,int> a,b;
	a = hsh[1].Query(l1,r1);b = hsh[0].Query(l2,r2);
	return a == b;
}
void Init() {
	scanf("%s",T + 1);
	lenT = strlen(T + 1);
	for(int i = 1 ; i <= lenT ; ++i) T[i + lenT] = T[i];
	int p = 1,q = 2,k = 1;

	while(q <= lenT) {
		if(T[p + k - 1] == T[q + k - 1]) ++k;
		else if(T[p + k - 1] > T[q + k - 1]) {p = max(p + k,q + 1);k = 1;}
		else if(T[p + k - 1] < T[q + k - 1]) {q = q + k;k = 1;}
		if(p > q) swap(p,q);
	}
	cycle(T,p,lenT);
	e[0][0] = e[1][0] = 1;
	for(int i = 1 ; i <= 2000000 ; ++i) {
		for(int j = 0 ; j <= 1 ; ++j) {
			e[j][i] = 1LL * e[j][i - 1] * ba[j] % mod[j];
		}
	}

	hsh[0].Init(T,lenT);

	int maxpos = 0;
	for(int i = 2 ; i <= lenT ; ++i) {
		if(maxpos) exd[i] = min(exd[i - maxpos + 1],exd[maxpos] + maxpos - i);
		while(i + exd[i] <= lenT && T[i + exd[i]] == T[exd[i] + 1]) ++exd[i];
		if(exd[i] && i + exd[i] - 1 > maxpos + exd[maxpos] - 1) maxpos = i;
	}
}
int main() {
#ifdef ivorysi
	freopen("f1.in","r",stdin);
#endif
	Init();

	read(N);

	for(int i = 1 ; i <= N ; ++i) {
		scanf("%s",S + 1);
		lenS = strlen(S + 1);
		hsh[1].Init(S,lenS);
		int p = 1,k = 0 ;
		int ans = 0;
		for(int j = 1 ; j <= lenS - lenT + 1; ++j) {
			while(p + k  <= j + lenT - 1) {
				if(T[k + 1] == S[p + k]) ++k;
				else if(T[k + 1] < S[p + k]) {p = p + k + 1;k = 0;}
				else if(T[k + 1] > S[p + k]) {
					bool f = 0;
					for(int h = 2 ; h <= k ; ++h) {
						if(h + exd[h] - 1 >= k) {
							p = p + h - 1;
							k = k - h + 1;
							f = 1;
							break;
						}
					}
					if(!f) {
						if(k != 0) {p = p + k;k = 0;}
						else {++p; k = 0;}
					}
				}
			}
			//printf("j:%d p:%d k:%d\n",j,p,k);
			if(check(j,p - 1,k + 1,lenT)) {
				++ans;
				//printf("j:%d\n",j);
			}

			if(p == j) {
				for(int h = 2 ; h <= lenT ; ++h) {
					if(h + exd[h] - 1 >= lenT) {
						p = p + h - 1;
						k = j + lenT - p;
						break;
					}
				}
				if(p == j) {p = j + lenT;k = 0;}
			}
		}
		out(ans);enter;
	}
}
上一篇:LeetCode392. 判断子序列


下一篇:Educational Codeforces Round 102 (Rated for Div. 2)