KMP學習文章,很好懂的兩篇文章
oiwiki 一篇
還有這篇
KMP
一種高效的字符匹配算法
前綴函數定義
給定一個長度爲n的字符串,其前綴函數是一個數組
表示其中 爲既是子串 的前綴同時也是該子串的後綴的最長真前綴(proper prefix)長度。真前綴代表即是子串的前綴但不等於子串
例如
求前綴函數代碼
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;
}
重要的是要怎麼用的問題了
問題一:給定一個字符串和,問是不是的一個子串
解決:構造成一個字符串’#’(’#'爲沒有在字符串和中出現的, 丟進去求前前綴和就行啦。
令則存在前綴函數等於的就說明是的一個子串
問題二:壓縮字符串
給定一個長度爲字符串 ,對其壓縮到最短,即把分成份等份相同的字符串,如可以壓縮爲
令若則爲最短的壓縮後的字符串
否則只能壓縮成
問題三:一個字符串中本質不同子串的數目
給定一個長度爲的字符串 ,計算其本質不同子串的數目
以迭代的辦法來求,若知道了當前的本質不同子串的數目,在其末尾加一個字符c後重新計算該數目的辦法
構造字符串,並將其反轉~ ,現在我們的任務變爲計算有多少~ 的前綴未在 的其餘任何地方出現。如果我們計算了 ~ 的前綴函數最大值 ,那麼最長的出現在 中的前綴其長度爲 。自然的,所有更短的前綴也出現了。
因此,當添加了一個新字符後新出現的子串數目爲 。
POJ3167
題意:
意思是給你一個的序列,然後再給你1-k等級的子序列,問你有多少個符合的子串,並輸出各自子串的起始位置,如例子,輸入 ,然後輸入串爲 然後輸入子串等級 爲 ,子串的意思是等級越高數越大,相同等級的數要一樣,所以子串可以變成 2 10 10 7 3 2
題解:將原序列看成個等級,記錄到每個等級的個數,
則兩個子串相同的條件就是對於每個字符,比它等級小的字符個數相同且相同字符等級的個數相同即可
由於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
題意:對長度爲的字符串(字符串下標從0開始)
區間dp+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);}
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<<
}