KMP学习文章,很好懂的两篇文章
oiwiki 一篇
还有这篇
KMP
一种高效的字符匹配算法
前缀函数定义
给定一个长度为n的字符串
s
s
s,其前缀函数是一个数组
π
π
π
π
[
i
]
π[i]
π[i]表示其中
s
[
0
,
1
,
2
⋯
i
]
s[0,1,2\cdots i]
s[0,1,2⋯i] 为既是子串 的前缀同时也是该子串的后缀的最长真前缀(proper prefix)长度。真前缀代表即是子串的前缀但不等于子串
例如
s
=
"
a
b
c
a
b
d
a
b
c
"
s="a b c a b d a b c"
s="abcabdabc"
π
=
"
000120123
"
π="000120123"
π="000120123"
求前缀函数代码
vector<int> prefix(string s) {
int n = (int)s.length();
vector<int> pi(n);
for (int i = 1; i < n; i++) {
int j = pi[i - 1];
while (j > 0 && s[i] != s[j]) j = pi[j - 1];
if (s[i] == s[j]) j++;
pi[i] = j;
}
return pi;
}
重要的是要怎么用的问题了
问题一:给定一个字符串
t
t
t和
s
s
s,问
s
s
s是不是
t
t
t的一个子串
解决:构造成一个字符串
s
+
s+
s+’#’
+
t
+t
+t(’#'为没有在字符串
s
s
s和
t
t
t中出现的, 丢进去求前前缀和就行啦。
令
n
=
s
.
l
e
n
g
t
h
(
)
n=s.length()
n=s.length()则存在前缀函数等于
n
n
n的就说明
s
s
s是
t
t
t的一个子串
问题二:压缩字符串
给定一个长度为 n n n字符串 s s s ,对其压缩到最短,即把 s s s分成 m m m份等份相同的字符串,如 s = " a b c a b c a b c " s="abcabcabc" s="abcabcabc"可以压缩为 " a b c " "abc" "abc"
令 k = n − π ( n − 1 ) k=n-π(n-1) k=n−π(n−1)若 k ∣ n k|n k∣n则 s [ 0 , 1 , 2 ⋯ k − 1 ] s[0,1,2\cdots k-1] s[0,1,2⋯k−1]为最短的压缩后的字符串
否则 s s s只能压缩成 s s s
问题三:一个字符串中本质不同子串的数目
给定一个长度为 n n n的字符串 s s s ,计算其本质不同子串的数目
以迭代的办法来求,若知道了当前的本质不同子串的数目,在其末尾加一个字符c后重新计算该数目的办法
构造字符串 t = s + c t=s+c t=s+c,并将其反转~ t t t ,现在我们的任务变为计算有多少~ t t t 的前缀未在 的其余任何地方出现。如果我们计算了 ~ t t t 的前缀函数最大值 ,那么最长的出现在 s s s中的前缀其长度为 π m a x π_{max} πmax。自然的,所有更短的前缀也出现了。
因此,当添加了一个新字符后新出现的子串数目为 ∣ s ∣ + 1 − π m a x |s|+1-π_{max} ∣s∣+1−πmax 。
POJ3167
题意:
意思是给你一个
1
−
S
1-S
1−S的序列,然后再给你1-k等级的子序列,问你有多少个符合的子串,并输出各自子串的起始位置,如例子,输入
N
,
K
,
S
N,K,S
N,K,S ,
9
,
6
,
10
9,6,10
9,6,10然后输入串
t
t
t为
5
,
6
,
2
,
10
,
10
,
7
,
3
,
2
,
9
5 ,6, 2, 10, 10, 7 ,3, 2, 9
5,6,2,10,10,7,3,2,9然后输入子串等级
s
s
s 为
1
,
4
,
4
,
3
,
2
,
1
1,4, 4, 3, 2, 1
1,4,4,3,2,1,子串的意思是等级越高数越大,相同等级的数要一样,所以子串可以变成 2 10 10 7 3 2
题解:将原序列
1
−
−
S
1--S
1−−S看成
S
S
S个等级,记录到
t
[
i
]
t[i]
t[i]每个等级的个数,
则两个子串相同的条件就是对于每个字符,比它等级小的字符个数相同且相同字符等级的个数相同即可
由于S才25,所以可以暴力存一下,利用一下前缀和即可判断两个字符等不等价,然后就变成了问题一了。对于’#'处小心处理即可
#include<algorithm>
#include<iostream>
#include<cstring>
#define ll long long
#define endl '\n'
const int MX=2e5+7;
using namespace std;
int A[MX],top;
struct node
{
int pre[26],val;
void get(){cin>>val;}
}pa[MX],pb[MX];
int pi[MX],K;
bool cmp(node a,node b,int i,int j)
{
if(i>=K)return false;
if(j-i-1<K&&j>K)return false;
int x=b.val,y=a.val,ans=0;
for(int k=1;k<x;k++)ans+=b.pre[k]-pa[j-i-1].pre[k];
int res=0;
for(int k=1;k<y;k++)res+=a.pre[k];
if(ans!=res)return false;
ans=b.pre[x]-pa[j-i-1].pre[x];
res=a.pre[y];
if(ans!=res)return false;
return true;
}
void prefix(node *pa, int n) {
pi[0] = 0;
for (int i = 1; i < n; i++) {
int j = pi[i - 1];
while (j > 0 && !cmp(pa[j],pa[i],j,i)) j = pi[j - 1];
if (cmp(pa[j],pa[i],j,i)) j++;
pi[i] = j;
}
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0);
int N,S;
cin>>N>>K>>S;
for(int i=0;i<N;i++)pb[i].get();
for(int i=0;i<K;i++)pa[i].get();
pa[K].val=30;
for(int i=K+1,e=0;e<N;e++)pa[i+e]=pb[e];
memset(pa[0].pre,0,sizeof(pa[0].pre));
pa[0].pre[pa[0].val]=1;
for(int i=1;i<=K+N;i++)
{
for(int j=1;j<=S;j++)pa[i].pre[j]=pa[i-1].pre[j];
pa[i].pre[pa[i].val]++;
}
prefix(pa,K+N+1);
for(int i=K+1;i<K+N+1;i++)if(pi[i]==K)A[++top]=i-2*K+1;
cout<<top<<endl;
for(int i=1;i<=top;i++)cout<<A[i]<<endl;
}
UVA455
即是问题二了
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define per(i,r,l) for(int i=r;i>=l;i--)
const int MX=4e2+7;
const int mod=9901;
using namespace std;
int p[MX],k[MX];
ll qpow(ll a,ll b,ll MOD=mod){for(ll ans=1;;a=a*a%MOD,b>>=1){if(b&1)ans=ans*a%MOD;if(!b)return ans;}}
ll inv(ll a,ll MOD=mod){return qpow(a,MOD-2,MOD);}
ll __gcm(ll a,ll b){return a*b/__gcd(a,b);}
vector<int> prefix_function(string s) {
int n = (int)s.length();
vector<int> pi(n);
for (int i = 1; i < n; i++) {
int j = pi[i - 1];
while (j > 0 && s[i] != s[j]) j = pi[j - 1];
if (s[i] == s[j]) j++;
pi[i] = j;
}
return pi;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0);
string s;
int t;
cin>>t;
while(t--){
cin>>s;
int n=s.length();
vector<int>pi=prefix_function(s);
int k=n-pi[n-1];
if(n%k!=0)k=n;
cout<<k<<endl;
if(t>=1)cout<<endl;
}
}
UVA11022
题意:对长度为
n
n
n的字符串(字符串下标从0开始)
区间dp+KMP
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为字符串
s
[
i
,
i
+
1
,
i
+
2
⋯
j
]
s[i,i+1,i+2\cdots j]
s[i,i+1,i+2⋯j]压缩后最短的长度
则转态转移为
设
p
r
e
f
i
x
[
i
]
[
j
]
prefix[i][j]
prefix[i][j]为
s
[
i
,
i
+
1
,
i
+
2
,
⋯
j
]
s[i,i+1,i+2,\cdots j]
s[i,i+1,i+2,⋯j]按问题二压缩后的长度
则
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
]
[
k
]
+
d
p
[
k
+
1
]
[
j
]
,
d
p
[
i
]
[
i
+
p
r
e
f
i
x
[
i
]
[
j
]
−
1
]
)
dp[i][j]=min(dp[i][k]+dp[k+1][j],dp[i][i+prefix[i][j]-1])
dp[i][j]=min(dp[i][k]+dp[k+1][j],dp[i][i+prefix[i][j]−1])
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define per(i,r,l) for(int i=r;i>=l;i--)
const int MX=4e2+7;
const int mod=9901;
using namespace std;
int p[MX],k[MX];
int dp[MX][MX],prefix[MX][MX];
ll qpow(ll a,ll b,ll MOD=mod){for(ll ans=1;;a=a*a%MOD,b>>=1){if(b&1)ans=ans*a%MOD;if(!b)return ans;}}
ll inv(ll a,ll MOD=mod){return qpow(a,MOD-2,MOD);}
ll __gcm(ll a,ll b){return a*b/__gcd(a,b);}
void prefix_function(char*s,int k) {
int n = strlen(s);
vector<int> pi(n);
pi[0]=0;
for (int i = 1; i < n; i++) {
int j = pi[i - 1];
while (j > 0 && s[i] != s[j]) j = pi[j - 1];
if (s[i] == s[j]) j++;
pi[i] = j;
}
for(int i=0;i<n;i++)
{
int m=i+k;
if((i+1)%(i+1-pi[i])==0)
prefix[k][m]=i+1-pi[i];
else prefix[k][m]=i+1;
}
return ;
}
int main()
{
//ios::sync_with_stdio(0),cin.tie(0);
char s[100];
int t;
while(cin>>s&&s[0]!='*'){
int n=strlen(s);
for(int i=0;i<n;i++)
{
prefix_function(s+i,i);
}
for(int i=0;i<n;i++)dp[i][i]=1;
for(int len=2;len<=n;len++)
{
for(int i=0;i+len<=n;i++)
{
int j=i+len-1;
dp[i][j]=len;
for(int k=i;k<j;k++)
{
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
}
dp[i][j]=min(dp[i][j],dp[i][i+prefix[i][j]-1]);
}
}
cout<<dp[0][n-1]<<endl;
}
}
UVA11452
题解:用KMP找到前两段的末尾部分,也就找到了循环节,则从那开始往后补齐八个字符即可
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define per(i,r,l) for(int i=r;i>=l;i--)
const int MX=4e2+7;
const int mod=9901;
using namespace std;
int p[MX],k[MX];
int dp[MX][MX],prefix[MX][MX];
ll qpow(ll a,ll b,ll MOD=mod){for(ll ans=1;;a=a*a%MOD,b>>=1){if(b&1)ans=ans*a%MOD;if(!b)return ans;}}
ll inv(ll a,ll MOD=mod){return qpow(a,MOD-2,MOD);}
ll __gcm(ll a,ll b){return a*b/__gcd(a,b);}
vector<int> prefix_function(string s) {
int n = s.length();
vector<int> pi(n);
pi[0]=0;
for (int i = 1; i < n; i++) {
int j = pi[i - 1];
while (j > 0 && s[i] != s[j]) j = pi[j - 1];
if (s[i] == s[j]) j++;
pi[i] = j;
}
return pi;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0);
//char s[100];
int t;
cin>>t;
string s;
while(t--){
cin>>s;
int n=s.length();
//cout<<n<<endl;
vector<int>pi=prefix_function(s);
int tmp=0;
//cout<<tmp<<endl;
for(int i=n-1;i>=0;i--)
{
if((i+1)%(i+1-pi[i])==0&&(i+1)/(i+1-pi[i])==2)
{
tmp=i;
break;
}
}
int m=tmp/2+1;
for(int i=0;i<8;i++)
{
cout<<s[(n+i)%m];
}
cout<<"..."<<endl;
}
}
MUH and Cube Walls
对于两个字符串匹配的条件是对于每一个字符,相邻字符差相等即可匹配,然后就变成了问题二了,用KMP搞一下就行了
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define per(i,r,l) for(int i=r;i>=l;i--)
const int MX=4e5+7;
const int mod=9901;
using namespace std;
ll qpow(ll a,ll b,ll MOD=mod){for(ll ans=1;;a=a*a%MOD,b>>=1){if(b&1)ans=ans*a%MOD;if(!b)return ans;}}
ll inv(ll a,ll MOD=mod){return qpow(a,MOD-2,MOD);}
ll __gcm(ll a,ll b){return a*b/__gcd(a,b);}
int a[MX],b[MX];
void prefix_function(int*s,int w,int n) {
//int n = s.length();
vector<int> pi(n);
pi[0]=0;
for (int i = 1; i <n; i++) {
int j = pi[i - 1];
while (j > 0 && s[i] != s[j]) j = pi[j - 1];
if (s[i] == s[j]||(j==0&&i!=w)) j++;
pi[i] = j;
}
int ans=0;
// cout<<w<<" "<<n<<endl;
for(int i=w+1;i<n;i++)
{
// cout<<pi[i]<<endl;
if(pi[i]==w)ans++;
}
cout<<ans<<endl;
return ;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0);
int n,w;
cin>>n>>w;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
for(int i=0;i<w;i++)
cin>>b[i];
b[w]=2e9;
for(int i=w,e=1;e<=n;e++)
{
b[i+e]=a[e-1];
}
for(int i=w+n;i>=1;i--)
{
if(i==w||i==w+1)continue;
b[i]=b[i]-b[i-1];
// cout<<"i="<<i<<" b="<<b[i]<<endl;
}
prefix_function(b,w,w+n+1);
//cout<<
}