Wannafly挑战赛11
D 白兔的字符串
白兔有一个字符串T。白云有若干个字符串S1,S2..Sn。
白兔想知道,对于白云的每一个字符串,它有多少个子串是和T循环同构的。
提示:对于一个字符串a,每次把a的第一个字符移动到最后一个,如果操作若干次后能够得到字符串b,则a和b循环同构。
所有字符都是小写英文字母
输入描述:
第一行一个字符串T(|T|<=10^6)
第二行一个正整数n (n<=1000)
接下来n行为S1~Sn (|S1|+|S2|+…+|Sn|<=10^7),max(|S1|,|S2|,|S3|,|S4|,..|Sn|)<=10^6
输出描述:
输出n行表示每个串的答案
示例1
输入
abab 2 abababab ababcbaba
输出
5 2
tags:字符串 Hash
把 T 在 Hash 后存在 set 里,再对每个 S 找子串。但要用 unordered_set ,否则会超时。
#include<bits/stdc++.h> using namespace std; #pragma comment(linker, "/STACK:102400000,102400000") #define rep(i,a,b) for (int i=a; i<=b; ++i) #define per(i,b,a) for (int i=b; i>=a; --i) #define mes(a,b) memset(a,b,sizeof(a)) #define INF 0x3f3f3f3f #define MP make_pair #define PB push_back #define fi first #define se second typedef long long ll; typedef unsigned long long ull; const int N = 1000005, Base=1e9+7; unordered_set< ull > Set; ull p[N]; void Init(char* T) { int len = strlen(T); ull Hash = 0; p[0] = 1; rep(i,0,len-1) { Hash = Hash*Base+T[i]; p[i+1] = p[i]*Base; } rep(i,0,len-1) { Hash = (Hash-T[i]*p[len-1])*Base + T[i]; Set.insert(Hash); } } int solve(char* S, int lenT) { int lenS = strlen(S); if(lenS < lenT) return 0; ull Hash = 0; int ret = 0; rep(i,0,lenT-1) Hash = Hash*Base+S[i]; if(Set.count(Hash)) ++ret; rep(i,lenT,lenS-1) { Hash = (Hash-S[i-lenT]*p[lenT-1])*Base + S[i]; if(Set.count(Hash)) ++ret; } return ret; } char T[N], S[N]; int main() { scanf("%s", T); Init(T); int lenT = strlen(T); int n; scanf("%d", &n); while(n--) { scanf("%s", S); printf("%d ", solve(S, lenT)); } return 0; }
正解是:
标算是ex_kmp。用R[i]表示S[i..len]和T的最长公共前缀,L[i]表示S[1..i]和T的最长公共后缀,当L[i]+R[i+1]>=|T|时,可以给一段区间打上标记。如果T的一个表示法在某个位置出现则至少会被一个标记覆盖。然后就变成了区间加法,求有多少个正数。用差分+前缀和即可。