Z函数学习笔记(扩展KMP)

ex-KMP(Z函数)


下标全部从1开始

Z函数可以解决的问题:
  1. 线 性 求 解 字 符 串 s t r 以 第 i 位 开 始 的 后 缀 与 s t r 的 最 长 公 共 前 缀 ( l c p ) 线性求解字符串str以第i位开始的后缀与str的最长公共前缀(lcp) 线stristr(lcp)

  2. Z [ i ] 表 示 第 i 位 开 始 的 后 缀 与 s t r 的 最 长 公 共 前 缀 Z[i]表示第i位开始的后缀与str的最长公共前缀 Z[i]istr


Z Box引入
  1. 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 Boxstr[l,r]str[l,r]str()i
  2. 在 位 置 i 时 , [ l , r ] 必 须 包 含 i , r 要 尽 可 能 的 大 在位置i时,[l,r]必须包含i,r要尽可能的大 i[l,r]i,r

在这里插入图片描述

ex-KMP写法
  1. 如何计算 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 i1Z BoxZ[i],iZ Box.

  2. 分为三种情况

    1. 在这里插入图片描述

    1. 在这里插入图片描述

    1. 在这里插入图片描述
时间复杂度
  1. 求Z[i],需要从1~n遍历,O(n)
  2. Z Box右端点最多右移n次,O(n)
  3. 总体复杂度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
  1. 给出两个字符串str1,str2,求str1的每一个后缀与str2的最长公共前缀

  2. O ( n ) O(n) O(n)

  3. 设 e x t [ i ] 表 示 s t r 1 第 i 位 开 始 的 后 缀 和 s t r 2 的 最 长 公 共 前 缀 ( l c p ) 设ext[i]表示str1第i位开始的后缀和str2的最长公共前缀(lcp) ext[i]str1istr2(lcp)

  4. 类 比 Z   b o x 开 一 个 e x t   b o x , 类比Z \ box开一个ext\ box, Z boxext 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。
    发现字符串的规律还是很难啊!
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值