ex-KMP(Z函数)
下标全部从1开始
Z函数可以解决的问题:
-
线 性 求 解 字 符 串 s t r 以 第 i 位 开 始 的 后 缀 与 s t r 的 最 长 公 共 前 缀 ( l c p ) 线性求解字符串str以第i位开始的后缀与str的最长公共前缀(lcp) 线性求解字符串str以第i位开始的后缀与str的最长公共前缀(lcp)
-
Z [ i ] 表 示 第 i 位 开 始 的 后 缀 与 s t r 的 最 长 公 共 前 缀 Z[i]表示第i位开始的后缀与str的最长公共前缀 Z[i]表示第i位开始的后缀与str的最长公共前缀
Z Box引入
- Z B o x 是 字 符 串 s t r 中 的 一 个 区 间 [ l , r ] 满 足 s t r [ l , r ] 是 s t r 的 前 缀 ( 不 一 定 要 求 最 长 ) , 随 i 移 动 而 变 化 Z \ Box是字符串str中的一个区间[l,r]满足str[l,r]是str的前缀(不一定要求最长),随i移动而变化 Z Box是字符串str中的一个区间[l,r]满足str[l,r]是str的前缀(不一定要求最长),随i移动而变化
- 在 位 置 i 时 , [ l , r ] 必 须 包 含 i , r 要 尽 可 能 的 大 在位置i时,[l,r]必须包含i,r要尽可能的大 在位置i时,[l,r]必须包含i,r要尽可能的大
ex-KMP写法
-
如何计算 Z Z Z数组(真的和KMP非常像),因为有 Z [ 1 ] = n Z[1]=n Z[1]=n,利用递推的思想,所以递推求解,假设 已 知 i − 1 位 置 的 Z B o x 的 情 况 下 我 们 来 计 算 Z [ i ] , 和 i 位 置 的 Z B o x 已知i-1位置的Z\ Box 的情况下我们来计算Z[i],和i位置的Z\ Box 已知i−1位置的Z Box的情况下我们来计算Z[i],和i位置的Z Box.
-
分为三种情况
时间复杂度
- 求Z[i],需要从1~n遍历,O(n)
- Z Box右端点最多右移n次,O(n)
- 总体复杂度O(n)
参考b站Z函数的代码
void Z(int n,char *str){
int l=0,r=0;//定义Z box
Z[1]=n;
for(int i=2;i<=n;i++){
if(i>r){//超过了Z box范围
while(str[i+Z[i]]==str[1+Z[i]])
Z[i]++;
l=i,r=i+Z[i]-1;
}
else if(Z[i-l+1]<r-i+1) //O(1)
Z[i]=Z[i-l+1];
else{
Z[i]=r-i;
while(str[i+Z[i]]==str[1+Z[i]])
Z[i]++;
l=i,r=i+Z[i]-1;
}
}
}
Z函数扩展有扩展KMP
-
给出两个字符串str1,str2,求str1的每一个后缀与str2的最长公共前缀
-
O ( n ) O(n) O(n)
-
设 e x t [ i ] 表 示 s t r 1 第 i 位 开 始 的 后 缀 和 s t r 2 的 最 长 公 共 前 缀 ( l c p ) 设ext[i]表示str1第i位开始的后缀和str2的最长公共前缀(lcp) 设ext[i]表示str1第i位开始的后缀和str2的最长公共前缀(lcp)
-
类 比 Z b o x 开 一 个 e x t b o x , 类比Z \ box开一个ext\ box, 类比Z box开一个ext box,
参考b站ext-kmp函数的代码
void get_ext(int n,char *str1,int m,char *str2){
int l=0,r=0;//定义Z box
for(int i=1;i<=n;i++){
if(i>r){//超过了Z box范围
while(1+ext[i]<=m&&i+ext[i]<=n&&str1[i+ext[i]]==str2[1+ext[i]])
ext[i]++;
l=i,r=i+ext[i]-1;
}
else if(Z[i-l+1]<r-i+1) //O(1)
ext[i]=Z[i-l+1];
else{
ext[i]=r-i;
while(1+ext[i]<=m&&i+ext[i]<=n&&str1[i+ext[i]]==str2[1+ext[i]])
ext[i]++;
l=i,r=i+ext[i]-1;
}
}
}
Problem:P5410 【模板】扩展 KMP(Z 函数)
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#include <sstream>
#define pb push_back
#define in insert
#define mem(f, x) memset(f,x,sizeof(f))
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
typedef pair<int,int>PII;
typedef pair<long,long>PLL;
typedef long long ll;
typedef unsigned long long ull;
const int N=2e7+10;
ll n,m,_;
int Z[N],ext[N];
char a[N],b[N];
void get_Z(int n,char *str){
int l=0,r=0;//定义Z box
Z[1]=n;
for(int i=2;i<=n;i++){
if(i>r){//超过了Z box范围
while(str[i+Z[i]]==str[1+Z[i]])
Z[i]++;
l=i,r=i+Z[i]-1;
}
else if(Z[i-l+1]<r-i+1) //O(1)
Z[i]=Z[i-l+1];
else{
Z[i]=r-i;
while(str[i+Z[i]]==str[1+Z[i]])
Z[i]++;
l=i,r=i+Z[i]-1;
}
}
}
void get_ext(int n,char *str1,int m,char *str2){
int l=0,r=0;//定义Z box
for(int i=1;i<=n;i++){
if(i>r){//超过了Z box范围
while(1+ext[i]<=m&&i+ext[i]<=n&&str1[i+ext[i]]==str2[1+ext[i]])
ext[i]++;
l=i,r=i+ext[i]-1;
}
else if(Z[i-l+1]<r-i+1) //O(1)
ext[i]=Z[i-l+1];
else{
ext[i]=r-i;
while(1+ext[i]<=m&&i+ext[i]<=n&&str1[i+ext[i]]==str2[1+ext[i]])
ext[i]++;
l=i,r=i+ext[i]-1;
}
}
}
int main()
{
scanf("%s%s",a+1,b+1);
n=strlen(a+1),m=strlen(b+1);
get_Z(m,b);//别传错了
get_ext(n,a,m,b);
ll suma=0,sumb=0;
for(int i=1;i<=m;i++){
suma^=(i*(Z[i]+1));
}
cout<<suma<<endl;
for(int i=1;i<=n;i++){
sumb^=(i*(ext[i]+1));
}
cout<<sumb<<endl;
return 0;
}
Problem: CF1537E1 Erase and Extend (Easy Version and Hard Version)
给一个字符串s,有两种操作:1.删去最后一个字符。2.复制自身,s=s+s。
可以随意进行操作,也可以不进行操作,找到s进行操作侯获得得长度为k得字符串中字典序最小得字符串。
1<=n,k<=5000(easy 版本)
1<=n,k<=5e5 (hard 版本)
贪心得到所有可能得前缀,再比较O(n*k)
// Problem: CF1537E1 Erase and Extend (Easy Version)
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1537E1
// Memory Limit: 250 MB
// Time Limit: 2000 ms
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <queue>
#include <set>
#include <map>
#include <vector>
#include <sstream>
#define pb push_back
#define in insert
#define mem(f, x) memset(f,x,sizeof(f))
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
typedef pair<int,int>PII;
typedef pair<long,long>PLL;
typedef long long ll;
typedef unsigned long long ull;
const int N=2e5+10,M=1e9+7;
ll n,m,_,k;
string get(string s,int k){
while(s.size()<k){
s+=s;
}
while(s.size()>k)
s.pop_back();
return s;
}
void solve()
{
string s;cin>>n>>k>>s;
string minn="",pre="";
pre+=s[0];
minn=get(pre,k);
for(int i=1;i<n;i++){
if(s[i]>s[0])break;
pre+=s[i];
minn=min(minn,get(pre,k));
}
cout<<minn<<endl;
}
int main()
{
solve();
return 0;
}
https://blog.csdn.net/weixin_43960414/article/details/118095536
给了两种很Nb的做法,一个是Z函数,一个是trick。
发现字符串的规律还是很难啊!