Z函数(扩展kmp)
BB
还好qhd没考Z函数,要不然人都没了 QAQ
Introduction
Z函数,国内称为 扩展kmp,可以在O(n)
时间内求得所有的i(i>1)
,lcp(s[i...len], s[1...len])
- OI-WIKI Z函数
https://oi-wiki.org/string/z-func/
Password - CodeForces 126B
Description
给定串s (|s| <= 1e6)
,在此串中找出一个最长串t
,使其既作为前缀出现,又作为后缀出现,同时在中间出现
Sample Input
fixprefixsuffix
abcdabc
Sample Output
fix
Just a legend
Solution - Z函数
首先跑Z函数后,把既是前缀又是后缀的长度放入set中
然后再次遍历这一字符串,如果对于某一位置i
,若i+z[i]-1==n
那么说明这一串是后缀,为了在中间出现,取m=z[i]-1
,否则取m=z[i]
,这里的m
就是这一位置作为中间串出现的可能的最长长度。在set中找到不超过m
的最大值,即是这一位置作为前缀和后缀,又在中间出现的串的最大值。遍历每一个位置,更新最大值及其位置即可
#include <bits/stdc++.h>
using namespace std;
const int N = (int)1e6 + 15;
typedef long long ll;
char s[N];
int z[N];
int arr[N];
inline void zFunc(char* s, int n) {
z[1] = 0;
for(int i = 2, l = 1, r = 1; i <= n; i++) {
z[i] = 0;
if(i <= r) {
z[i] = min(z[i - l + 1], r - i + 1);
}
while(i + z[i] <= n && s[1 + z[i]] == s[i + z[i]]) {
z[i]++;
}
if(i + z[i] - 1 > r) {
l = i;
r = i + z[i] - 1;
}
}
}
int main() {
while(~scanf("%s", s + 1)) {
int n = strlen(s + 1);
zFunc(s, n);
int k = 0;
for(int i = 2; i <= n; i++) {
if(i + z[i] - 1 == n) {
arr[++k] = z[i];
}
}
sort(arr + 1, arr + 1 + k);
int ans = -1, pos = -1;
for(int i = 2; i <= n; i++) {
int m = z[i] - (i + z[i] - 1 == n);
auto it = upper_bound(arr + 1, arr + 1 + k, m);
if(it != arr + 1) {
it--;
if(ans < *it) {
ans = *it;
pos = i;
}
}
}
if(pos != -1) {
printf("%.*s\n", ans, s + pos);
} else {
puts("Just a legend");
}
}
}
Prefixes and Suffixes - Codeforces 432D
Description
给定串s (|s| <= 1e5)
,给出所有即作为前缀又作为后缀出现的串的出现次数
Sample Input
ABACABA
AAA
Sample Output
3
1 4
3 2
7 1
3
1 3
2 2
3 1
Solution - Z函数
跑完z函数后(建议z[1]=n
,方便处理),把z[i]
全部插入数组中。此后,对于每一个位置,数组中大于等于z[i]
的数量即为出现次数
#include <bits/stdc++.h>
using namespace std;
const int N = (int)1e6 + 15;
typedef pair<int, int> pii;
typedef long long ll;
char s[N];
int z[N];
int arr[N];
inline void zFunc(char* s, int n) {
z[1] = n;
for(int i = 2, l = 1, r = 1; i <= n; i++) {
z[i] = 0;
if(i <= r) {
z[i] = min(z[i - l + 1], r - i + 1);
}
while(i + z[i] <= n && s[1 + z[i]] == s[i + z[i]]) {
z[i]++;
}
if(i + z[i] - 1 > r) {
l = i;
r = i + z[i] - 1;
}
}
}
int main() {
while(~scanf("%s", s + 1)) {
int n = strlen(s + 1);
zFunc(s, n);
for(int i = 1; i <= n; i++) {
arr[i] = z[i];
}
sort(arr + 1, arr + 1 + n);
vector<pii> vec;
for(int i = n, j = 1; i >= 1; i--, j++) {
if(i + z[i] - 1 == n) {
int cnt = arr + 1 + n - lower_bound(arr + 1, arr + 1 + n, z[i]);
vec.push_back(make_pair(j, cnt));
}
}
printf("%d\n", (int)vec.size());
for(const pii& pr : vec) {
printf("%d %d\n", pr.first, pr.second);
}
}
}
string matching - HDU 6629
Sample Input
3
_Happy_New_Year_
ywwyww
zjczzzjczjczzzjc
Sample Output
17
7
32
Solution 1 - kmp的next数组
实际上,只需要计算next数组,然后提前统计嵌套下的次数(类似前缀和),最后累加该值即是答案
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = (int)1e6 + 15;
const int inf = 0x3f3f3f3f;
char s[N];
int knxt[N], kcnt[N];
void getNext(char* p) {
knxt[0] = -1;
kcnt[0] = 1;
int k = -1, j = 0;
while(p[j]) {
if(k == -1 || p[k] == p[j]) {
j++;
k++;
//knxt[j] = (p[k] != p[j] ? k : knxt[k]);
knxt[j] = k;
kcnt[j] = kcnt[k] + 1;
} else {
k = knxt[k];
}
}
}
ll solve(char* s) {
getNext(s);
ll ans = 0;
for(int i = 0; s[i]; i++) {
ans += kcnt[i];
}
return ans;
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
scanf("%s", s);
printf("%lld\n", solve(s) - strlen(s));
}
}
Solution 2 - Z函数
然而,这不是一道Z函数裸题嘛QAQ
#include <bits/stdc++.h>
using namespace std;
const int N = (int)1e6 + 15;
typedef long long ll;
char s[N];
int z[N];
int arr[N];
inline void zFunc(char* s, int n) {
z[1] = 0;
for(int i = 2, l = 1, r = 1; i <= n; i++) {
z[i] = 0;
if(i <= r) {
z[i] = min(z[i - l + 1], r - i + 1);
}
while(i + z[i] <= n && s[1 + z[i]] == s[i + z[i]]) {
z[i]++;
}
if(i + z[i] - 1 > r) {
l = i;
r = i + z[i] - 1;
}
}
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
scanf("%s", s + 1);
int n = strlen(s + 1);
zFunc(s, n);
ll ans = 0;
for(int i = 2; i <= n; i++) {
ans += 1 + z[i] - (i + z[i] - 1 == n);
}
printf("%lld\n", ans);
}
}