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 的兄弟嘛……

浙公网安备 33010602011771号