【專題訓練】字串
阿新 • • 發佈:2019-08-07
KMP
相關
- 單個字串匹配演算法,對模式串預處理求出next陣列,匹配時是模式串根據next陣列進行跳轉。
- KMP可用於匹配子串出現的位置,次數。
- 更常見的應用是根據next陣列的性質,結合一些思維或者dp。
- \(next[i]\)即表示模式串前\(i\)個字元的最長字首字尾相等的長度。
- 也表示該最長相等字尾對應的字首的末位置,
i=next[i]
就是表示跳到該字首,常用。 i-nex[i]
則表示字首[0...i]的迴圈節大小(若有),i/(i-nex[i])
就是表示迴圈節個數。
- 也表示該最長相等字尾對應的字首的末位置,
題目
CF471D MUH and Cube Walls
題意
有一面小牆和一面大牆,小牆可上升下降,問在大牆中能匹配多少個。
分析
很裸的可重疊KMP匹配子串個數,因為牆可升可降,即模式串可同加同減,顯然不變的是差分陣列,對差分陣列進行kmp匹配即可,注意特判模式串長度為1的情況。
程式碼
#include <bits/stdc++.h> using namespace std; const int N=2e5+50; int a[N],b[N],n,m; int nex[N]; void init(int s[],int n){ int i=0,j=-1; nex[0]=-1; while(i<n){ if(j==-1 || s[i]==s[j]){ nex[++i]=++j; }else{ j=nex[j]; } } } int kmp(int s[],int n,int p[],int m){ int i=0,j=0; init(p,m); int cnt=0; while(i<n && j<m){ if(j==-1 || s[i]==p[j]){ i++; j++; }else{ j=nex[j]; } if(j==m){ cnt++; j=nex[j]; } } return cnt; } int main(void){ scanf("%d%d",&n,&m); for(int i=0;i<n;i++){ scanf("%d",&a[i]); } for(int i=0;i<n-1;i++){ a[i]=a[i+1]-a[i]; } for(int i=0;i<m;i++){ scanf("%d",&b[i]); } for(int i=0;i<m-1;i++){ b[i]=b[i+1]-b[i]; } int ans=kmp(a,n-1,b,m-1); if(m==1){ //注意特判 ans=n; } printf("%d\n",ans); return 0; }
hdu3336 Count the string
題意
近十年前的一道經典題,給定字串,求所有字首作為子串出現的次數之和。
分析
根據next陣列的性質,我們從後往前列舉字首,顯然當\(s[0...i-1]\)出現一次時,\(s[0...nex[i]-1]\)也出現了一次,因此定義dp狀態為\(dp[i]\)表示長度為i的前綴出現的次數,顯然轉移式為\(dp[i]+=1\),且\(dp[nex[i]]+=dp[i]\)。
程式碼
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
const int mod=10007;
int T,n;
char s[N];
int nex[N];
int dp[N];
void init(char *s,int n){
int i=0,j=-1;
nex[0]=-1;
while(i<n){
if(j==-1 || s[i]==s[j]){
nex[++i]=++j;
}else{
j=nex[j];
}
}
}
void solve(int n){
for(int i=n;i>=1;i--){
dp[i]++;
dp[i]%=mod;
dp[nex[i]]+=dp[i];
dp[nex[i]]%=mod;
}
}
int main(void){
// freopen("in.txt","r",stdin);
scanf("%d",&T);
while(T--){
memset(dp,0,sizeof(dp));
scanf("%d",&n);
scanf("%s",s);
init(s,n);
solve(n);
int ans=0;
for(int i=1;i<=n;i++){
ans=(ans+dp[i])%mod;
}
printf("%d\n",ans);
}
return 0;
}