20260723 - KMP 总结

前言

KMP 好难好难好难好难。

严肃忘记 KMP 是什么。严肃复习。

KMP

border 是神!!!

具体 KMP 是啥不想讲。我好累。

A - KMP

板子。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
using namespace std;
const int N = 2e6+5;
string a,b,s;
int nxt[N],n,m,k;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
int main(){
    cin>>b>>a;s=a+'#'+b;
    n=a.size(),m=b.size(),k=s.size();
    a=" "+a,b=" "+b,s=" "+s;
    for(int i=2;i<=k;i++){
        int j=i-1;
        while(j>0){
            if(s[nxt[j]+1]==s[i])
                {nxt[i]=nxt[j]+1;break;}
            j=nxt[j];
        }
    }for(int i=n+2;i<=k;i++)
        if(nxt[i]==n)cout<<i-2*n<<"\n";
    for(int i=1;i<=n;i++)cout<<nxt[i]<<" ";
	cout<<"\n";
    return 0;
}

B - Radio Transmission 无线传输

容易发现答案就是 \(n - nxt_n\)

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
using namespace std;
const int N = 1e6+5;
string s;
int nxt[N],n;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
int main(){
    n=read();
	cin>>s;s=" "+s;
    for(int i=2;i<=n;i++){
        int j=i-1;
        while(j>0){
            if(s[nxt[j]+1]==s[i])
                {nxt[i]=nxt[j]+1;break;}
            j=nxt[j];
        }
    }
    cout<<n-nxt[n]<<"\n";
    return 0;
}

C - MUH and Cube Walls

\(\inf\) 个世纪后才看懂题,又过了 \(\inf\) 个世纪才知道怎么做。我好菜。

事实上就是找 \(a\) 数组中长度为 \(w\) 的一段区间,使其与 \(b\) 数组的绝对差相等。换句话说是变化趋势相等。然后想到差分,把差分数组拎出来做 KMP 就行了。

特判一下 \(w=1\) 的情况。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
using namespace std;
const int N = 2e6+5;
int a[N],b[N],p[N],nxt[N],n,m,k,Ans;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=m;i++)b[i]=read();
	if(m==1){
		cout<<n<<"\n";
		return 0;
	}
    k=n+m-1;
    for(int i=1;i<m;i++)p[i]=b[i+1]-b[i];
    p[m]=-0x3f3f3f3f;
    for(int i=m+1;i<=k;i++)
        p[i]=a[i-m+1]-a[i-m];
    for(int i=2;i<=k;i++){
        int j=i-1;
        while(j>0){
            if(p[nxt[j]+1]==p[i])
                {nxt[i]=nxt[j]+1;break;}
            j=nxt[j];
        }
    }
    //for(int i=1;i<=k;i++)cout<<p[i]<<" ";cout<<"\n";
    for(int i=m+1;i<=k;i++)
        if(nxt[i]==m-1)Ans++;
    cout<<Ans<<"\n";
    return 0;
}

D - Compress Words

板子 KMP,找重叠部分即可。

但是直接做是 \(O(n^2)\) 喜提 TLE,不过每次不需要把 \(ans\) 全串放进去,只需要截取后缀 \(|s_i|\) 就行了。就线性了。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
using namespace std;
const int N = 1e5+5;
const int M = 2e6+5;
int n,nxt[M];
string s[N],Ans;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
string KMP__(string a,string b){
	string str=" "+b+"#"+a;
    int k=str.size()-1;
    for(int i=1;i<=k;i++)nxt[i]=0;
    for(int i=2;i<=k;i++){
        int j=i-1;
        while(j>0){
            if(str[nxt[j]+1]==str[i])
                {nxt[i]=nxt[j]+1;break;}
            j=nxt[j];
        }
    }string res="";
    for(int i=nxt[k];i<b.size();i++)res+=b[i];
	return res;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++)cin>>s[i];
	Ans=s[1];
	for(int i=2;i<=n;i++){
		string tmp="";
		int x=Ans.size(),y=s[i].size();
		for(int j=max(0,x-y);j<Ans.size();j++)tmp+=Ans[j];
		Ans+=KMP__(tmp,s[i]);
	}
	cout<<Ans<<"\n";
    return 0;
}

E - OKR-Periods of Words

一个必然可行的周期是 \(i-nxt_i\),但是由于 \(nxt_i\) 是最大的(border 的定义是这样的),这个周期不一定是最大的。因此我们还要不断跳 \(nxt\) 直到变得最小。

直接跳是平方的,记忆化一下即可。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
using namespace std;
const int N = 1e6+5;
LL n,nxt[N],Ans;
string s;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}

int main(){
	n=read();
	cin>>s;s=" "+s;
	for(int i=2;i<=n;i++){
        int j=i-1;
        while(j>0){
            if(s[nxt[j]+1]==s[i])
                {nxt[i]=nxt[j]+1;break;}
            j=nxt[j];
        }
    }
    for(int i=1;i<=n;i++){
    	int j=i;
    	while(nxt[j])j=nxt[j];//找到“最小”的 border 
    	if(nxt[i])nxt[i]=j;//记忆化 防止超时 
    	Ans+=i-j;
    }
    cout<<Ans<<"\n";
    return 0;
}

F - Password

要求找到可能的 \(nxt'_n\)(可以通过跳 \(nxt\) 获得多个 \(nxt'_n\))中最大且存在 \(2 \le i < n\) 使 \(nxt'_i = nxt'_n\)\(nxt'_n\)

显然 \(nxt'_i\)\(nxt_i\) 最优,那么这个可以用桶预处理出来,\(nxt'_n\) 可以 \(O(n)\) 逐个扫一遍,扫到一个合法的就 break,因为一定是最大的。

没一个合法的就无解。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
using namespace std;
const int N = 2e6+5;
string s;
bool flag=0;
int nxt[N],p[N],n,m,k,ans,t[N];
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
int main(){
	cin>>s;n=s.size(),s=" "+s;
    for(int i=2;i<=n;i++){
        int j=i-1;
        while(j>0){
            if(s[nxt[j]+1]==s[i])
                {nxt[i]=nxt[j]+1;break;}
            j=nxt[j];
        }
    }
    for(int i=1;i<n;i++)t[nxt[i]]++;
    int now=n;
    while(nxt[now]){
		now=nxt[now];//找到“最小”的 border 
		if(t[now])break;
	}
    if(!t[now])cout<<"Just a legend\n";
    else{
    	string tmp="";
    	for(int i=1;i<=now;i++)tmp+=s[i];
    	cout<<tmp<<"\n";
    }
    return 0;
}

G - Prefixes and Suffixes

首先求 \(nxt\)

然后你发现输出中可能的 \(l_i\) 一定是 \(nxt'_n\) 中的一个,跑出来就行,但是 \(c_i\) 怎么求?

建图(?),让 \(i\) 连向 \(nxt_i\),然后倒序枚举 \(i\)\(n\)\(1\),维护 \(cnt\) 数组,若存在边 \(i \to j\)\(cnt_j \gets cnt_j + cnt_i\)

然后 \(c_i\) 就是 \(cnt_{l_i}\) 了。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
using namespace std;
const int N = 1e6+5;
LL n,nxt[N],Ans,val[N],cnt[N];
string s;
vector<int> g[N];
stack<pii> stk;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}

int main(){
	cin>>s;
	n=s.size(),s=" "+s;
	for(int i=2;i<=n;i++){
        int j=i-1;
        while(j>0){
            if(s[nxt[j]+1]==s[i])
                {nxt[i]=nxt[j]+1;break;}
            j=nxt[j];
        }
    }
    //for(int i=1;i<=n;i++)cout<<i<<"->"<<nxt[i]<<"\n";
    for(int i=1;i<=n;i++)g[i].pb(nxt[i]);
    val[n]=1;
    for(int i=1;i<=n;i++)cnt[i]=1;
    for(int i=n;i>=1;i--){
    	if(val[i])stk.push(make_pair(i,cnt[i]));
    	for(int j=0;j<g[i].size();j++){
    		int x=g[i][j];
			val[x]+=val[i],cnt[x]+=cnt[i];
    	}
    }
    cout<<stk.size()<<"\n";
    while(!stk.empty()){
    	pii u=stk.top();stk.pop();
    	cout<<u.fr<<" "<<u.se<<"\n";
    }
    return 0;
}

H - Test

D。当然需要打表考虑 \(6\) 种不同的顺序情况。

但是这样不行啊,在某个串被另一个串完全包含的时候,可以把当前串省略掉。

就做完了。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
using namespace std;
const int N = 3e5+5;
int n,nxt[N],len;
string str[5],s[5],Ans;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
string KMP__(string a,string b){
	string t=" "+b+"#"+a;
    int k=t.size()-1;
    for(int i=1;i<=k;i++)nxt[i]=0;
    for(int i=2;i<=k;i++){
        int j=i-1;
        while(j>0){
            if(t[nxt[j]+1]==t[i])
                {nxt[i]=nxt[j]+1;break;}
            j=nxt[j];
        }
    }string res="";
    bool flag=0;
    for(int i=b.size()+2;i<=k;i++)
    	if(nxt[i]==b.size())return res;
    for(int i=nxt[k];i<b.size();i++)res+=b[i];
	return res;
}
void sol(){
	Ans=s[1];
	for(int i=2;i<=n;i++)Ans+=KMP__(Ans,s[i]);
	len=min(len,(int)Ans.size());return;
}
int main(){
	n=3;
	for(int i=1;i<=3;i++)cin>>str[i];
	len=str[1].size()+str[2].size()+str[3].size();
	s[1]=str[1],s[2]=str[2],s[3]=str[3];sol();
	s[1]=str[1],s[2]=str[3],s[3]=str[2];sol();
	s[1]=str[2],s[2]=str[1],s[3]=str[3];sol();
	s[1]=str[2],s[2]=str[3],s[3]=str[1];sol();
	s[1]=str[3],s[2]=str[1],s[3]=str[2];sol();
	s[1]=str[3],s[2]=str[2],s[3]=str[1];sol();
	cout<<len<<"\n";
    return 0;
}

I - 动物园

先正常求一遍 \(nxt\),并记录 \(cnt_i\) 表示 \(nxt'_i\) 的个数。

对于每个 \(i\),以不断跳 \(nxt\) 的形式找到能匹配上且 \(2j \le i\) 的最大的 \(j\),则题面中的 \(num_i\) 就是 \(cnt_j\) 了。

全部 \(+1\) 后乘起来就好了,记得开 long long

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
using namespace std;
const int N = 2e6+5;
const LL MOD = 1e9+7;
string s;
LL T,nxt[N],num[N],n,m,k,Ans;
vector<int> g[N];
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
int main(){
	T=read();
	while(T--){
		cin>>s;n=s.size(),s=" "+s;
		for(int i=1;i<=n;i++)nxt[i]=0,num[i]=0;
		num[1]=1;
	    for(int i=2;i<=n;i++){
	        int j=i-1;
	        while(j>0){
	            if(s[nxt[j]+1]==s[i])
	                {nxt[i]=nxt[j]+1;break;}
	            j=nxt[j];
	        }
	        num[i]=num[nxt[i]]+1;
	    }
	    Ans=1;
	    for(int i=2,j=0;i<=n;i++){
	    	while(j&&s[i]!=s[j+1])j=nxt[j];
	    	if(s[i]==s[j+1])j++;
	    	while(2*j>i)j=nxt[j];
	    	Ans=Ans*(num[j]+1)%MOD;
	    }
	    cout<<Ans<<"\n";
	}
    return 0;
}

J - Om Nom and Necklace

可以说的上是 Z 函数(exKMP)裸题,可惜我不会 Z 函数,并且 KMP 也能做。

首先你跑 KMP 求出 \(nxt\)(老套路)。

考虑前缀串长度为 \(i\),那么最小循环节长度为 \(len = i - nxt_i\),那么该前缀串包含的完整循环节数量为 \(tot = \frac{i}{len}\),分配给 \(k\) 份子串,每份子串拥有 \(\lfloor \frac{tot}{k} \rfloor\) 个完整的循环节。分完后剩下 \(tot \bmod k\) 个完整循环节,再加上前面一个不完整循环节 \(i \bmod len\),构成长度为 \(len (tot \bmod k) + i \bmod len\) 的后缀。

后缀长度不超过子串循环节长度就合法,也就是 \(len (tot \bmod k) + i \bmod len \le \lfloor \frac{tot}{k} \rfloor len\) 时合法。判断一下就行。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
using namespace std;
const int N = 1e6+5;
int n,k,nxt[N];
string s;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
int main(){
	n=read(),k=read();
	cin>>s;s=" "+s;
	for(int i=2;i<=n;i++){
        int j=i-1;
        while(j>0){
            if(s[nxt[j]+1]==s[i])
                {nxt[i]=nxt[j]+1;break;}
            j=nxt[j];
        }
	}
	for(int i=1;i<=n;i++){
		int len=i-nxt[i];
		int tot=i/len;
		if((tot/k)*len>=(tot%k)*len+(i%len))cout<<"1";
		else cout<<"0";
	}
	cout<<"\n";
    return 0;
}

总结

KMP 好难。

KMP 好吃。

怎么和昨天背包的总结话语一模一样只是反了个顺序啊 /yun。

不管怎么样,KMP 确实还是很好用的。

以及,exKMP 和 KMP 的关系到底是什么啊。exKMP 不是 Manacher 的兄弟嘛……

posted @ 2026-07-23 18:37  嘎嘎喵  阅读(29)  评论(7)    收藏  举报