KMP

Border

如果字符串 \(S\) 的同长度的前缀和后缀完全相同,即 \(Prefix[i] = Suffix[i]\)
则称此前缀(后缀)为一个 \(Border\)(根据语境,有时 \(Border\) 也指长度)。

特殊地,字符串本身也可以是它的 \(Border\),具体是不是根据语境判断。

周期和循环节

对于字符串 \(S\) 和正整数 \(p\),如果有 \(S[i] = S[i − p]\),对于 \(p < i ≤ |S|\)
立,则称 \(p\) 为字符串 \(S\) 的一个周期。
特殊地,\(p = |S|\) 一定是 \(S\) 的周期

若字符串 \(S\) 的周期 \(p\) 满足 \(p \:\: |\:\: |S|\),则称 \(p\)\(S\) 的一个循环节
特殊地,\(p = |S|\) 一定是 \(S\) 的循环节

重要性质

Border vs 周期

\(p\)\(S\) 的周期 ⇔ \(|S| − p\)\(S\)\(Border\)

证明

\(p 为 S 的周期 ⇔ S[i − p] = S[i]\)
\(q 为 S 的 Border ⇔ S[1, q] = S[|S| − q + 1, |S|] ⇔ S[1] = S[|S| − q + 1], S[2] = S[|S| − q + 2], . . . , S[q] = S[|S|]\)
\(所以|S|-q是一个周期\)
\(易得:p + q = |S|\)

因此,字符串的周期性质等价于 \(Border\) 的性质,
求周期也等价于求 \(Border\)

警告:\(Border\) 不具有二分性。

Border 的传递性

\(S\)\(Border\)\(Border\) 也是 \(S\)\(Border\)

\(S\) 的所有 \(Border\) 等价于求所有前缀的最大 \(Border\)

KMP

next数组

\(next[i]=Preffix[i]\)的非平凡(去掉字符串本身)的最大\(Border\)
\(next[1]=0\)

考虑 \(Prefix[i]\) 的所有(长度大于 \(1\) 的)\(Border\),去掉最后一个字母,就
会变成 \(Prefix[i − 1]\)\(Border\)

因此求 \(next[i]\) 的时候,可以遍历 \(Prefix[i − 1]\) 的所有 \(Border\),即
\(next[i − 1], next[next[i − 1]], . . . , 0,\)检查后一个字符是否等于 \(S[i]\)

复杂度分析

\(考虑使用势能分析进行讨论:\)
\(如果 next[i] = next[i − 1] + 1,则势能会增加 1\)
\(否则势能会先减少到某个 next[j],然后有 next[i] = next[j] + 1,势能 也会增加 1,在寻找 next[j] 的过程中,势能会减少,每次至少减少 1。\)
\(还有一种情况,next[i] = 0,势能清空,且不会增加。\)
\(综上,势能总量为 O(N),因此整体的复杂度也是 O(N),常数为 2 左右 (很小)。空间复杂度也为 O(N)。\)

实现

void init(string s){
    //s="!"+s' 使得s从[1,len] 
    len=s.length()-1;
    t=s;
    for(int i=2;i<=len;i++){
        nxt[i]=nxt[i-1];
        while(nxt[i] && s[i]!=s[nxt[i]+1])nxt[i]=nxt[nxt[i]];
        nxt[i]+=(s[i]==s[nxt[i]+1]);
    }	
    return ;
}

字符串匹配

struct KMP{
    int len,nxt[maxn];
    string t;
    void init(string s){
        //s="!"+s' 使得s从[1,len] 
        len=s.length()-1;
        t=s;
        for(int i=2;i<=len;i++){
            nxt[i]=nxt[i-1];
            while(nxt[i] && s[i]!=s[nxt[i]+1])nxt[i]=nxt[nxt[i]];
            nxt[i]+=(s[i]==s[nxt[i]+1]);
        }	
        return ;
    }

    vector<int> match(string s){
        //查看s中字符串t的位置 
        int len_s=s.length();
        int j=0;
        vector<int>ans;
        for(int i=0;i<len_s;i++){
            while(j && s[i]!=t[j+1])j=nxt[j];
            if(s[i]==t[j+1])j++;
            if(j==len){
                ans.pb(i+1-len+1);
                //记录起始位置 
                j=nxt[j];
            }
        }
        return ans;	
    } 
}; 

例题

1.栗酱的数列
image

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}

struct KMP{
	int len,nxt[maxn];
	vector<int> t;
	void init(vector<int> s){
		//s="!"+s' 使得s从[1,len] 
		for(int i=0;i<=len;i++)nxt[i]=0;
		len=s.size()-1;
		t=s;
		for(int i=2;i<=len;i++){
			nxt[i]=nxt[i-1];
			while(nxt[i] && s[i]!=s[nxt[i]+1])nxt[i]=nxt[nxt[i]];
			nxt[i]+=(s[i]==s[nxt[i]+1]);
		}	
		return ;
	}
	
	ll match(vector<int> s){
		//查看s中字符串t的位置 
		int len_s=s.size();
		int j=0;
		ll ans=0;
		for(int i=0;i<len_s;i++){
			while(j && s[i]!=t[j+1])j=nxt[j];
			if(s[i]==t[j+1])j++;
			if(j==len){
				ans++;
				j=nxt[j];
			}
		}
		return ans;	
	} 
	void debug(){
		for(auto i:t)cout<<i<<" ";puts("");
		for(int i=1;i<=len;i++)cout<<nxt[i]<<" ";puts("");
	}
}P; 

void solve(){
	int n=read(),m=read(),k=read();
	vector<int>a(n),b(m+1);
	for(int i=0;i<n;i++)a[i]=read();
	for(int i=1;i<=m;i++)b[i]=read();
	vector<int>aa,bb;
	for(int i=1;i<n;i++)aa.pb(((a[i]-a[i-1])%k+k)%k);
	for(int i=1;i<=m;i++)bb.pb(((-b[i]+b[i-1])%k+k)%k);
	P.init(bb);
	int ans=P.match(aa);
	cout<<ans<<endl;
	return ; 
}

int main(){
	int t=read();
	while(t--)solve();
	return 0;
}

Border 树

对于一个字符串 \(S,n = |S|,\)它的 \(Border\) 树(也叫 \(next 树\))共有 \(n + 1\)
个节点:\(0, 1, 2, . . . , n。\)
\(0\) 是这棵有向树的根。对于其他每个点 \(1 ≤ i ≤ n\) ,父节点为 \(next[i]\)

性质

  1. 每个前缀 \(Prefix[i]\) 的所有 \(Border\):节点 i 到根的链。
  2. 哪些前缀有长度为 \(x\)\(Border\)\(x\) 的子树。
  3. 求两个前缀的公共 \(Border\) 等价于求 \(LCA\)

例题

1.葫芦和斌斌的字符串1
运用第二个性质
查看那个长度为x的border的子树size大于等于k

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=5e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-8;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
struct KMP{
	int len,nxt[maxn];
	string t;
	void init(string s){
		//s="!"+s' 使得s从[1,len] 
		len=s.length()-1;
		t=s;
		for(int i=2;i<=len;i++){
			nxt[i]=nxt[i-1];
			while(nxt[i] && s[i]!=s[nxt[i]+1])nxt[i]=nxt[nxt[i]];
			nxt[i]+=(s[i]==s[nxt[i]+1]);
		}	
		return ;
	}
	
	vector<int> match(string s){
		//查看s中字符串t的位置 
		int len_s=s.length();
		int j=0;
		vector<int>ans;
		for(int i=0;i<len_s;i++){
			while(j && s[i]!=t[j+1])j=nxt[j];
			if(s[i]==t[j+1])j++;
			if(j==len){
				ans.pb(i+1-len+1);
				//记录起始位置 
				j=nxt[j];
			}
		}
		return ans;	
	} 
}T; 

int tot,head[maxn],nx[maxn],to[maxn];
void add(int x,int y){to[++tot]=y;nx[tot]=head[x];head[x]=tot;}
int n,k;
string a; 
int sz[maxn];
void dfs(int x,int fa){
	sz[x]=1;
	for(int i=head[x];i;i=nx[i]){
		int v=to[i];if(v==fa)continue;
		dfs(v,x);sz[x]+=sz[v];
	}
	return ;
} 
int main(){
	n=read();k=read();cin>>a;
	a="!"+a;T.init(a);
	for(int i=1;i<=n;i++){
		add(i,T.nxt[i]);
		add(T.nxt[i],i);
	}
	dfs(0,0);
	for(int i=n;i;i=T.nxt[i]){
		if(sz[i]>=k){
			for(int j=1;j<=i;j++)cout<<a[j];puts("");
			return 0;
		}
	}
	puts("-1");
    return 0;

}
posted @ 2022-09-29 22:20  I_N_V  阅读(29)  评论(0编辑  收藏  举报