kmp算法

 

https://blog.csdn.net/qq_37164003/article/details/77424776

讲了一下扩展kmp与kmp中数组的含义。

https://www.cnblogs.com/dilthey/p/8620119.html

重点讲的扩展kmp

 

 

 

https://blog.csdn.net/dl962454/article/details/79910744 详解

https://blog.csdn.net/qq_40938077/article/details/80460853

https://blog.csdn.net/liujiuxiaoshitou/article/details/70232219

 http://www.cnblogs.com/chenxiwenruo/p/3546457.html循环节

 

https://vjudge.net/contest/240809#problem/A习题链接

Number Sequence

 HDU - 1711 

//这道题的next数组是由优化的next数组写的,next数组的含义不是指最长前缀与后缀的长度,因为加了递归优化,值一般就是-1与0.
//求最开始的匹配位置。(题目意思)
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxm = 1e6 + 5; int a[maxm], b[maxm], nex[maxm]; int t, n, m, x; void getnext() { nex[0] = -1; int k = -1; int j = 0 ; while(j < m) { if(k == -1 || b[j] == b[k]) { j++, k++; if(b[j] != b[k]) nex[j] = k; else nex[j] = nex[k]; } else k = nex[k]; } } int kmps() { int i = 0, j = 0; getnext(); while(i < n && j < m) { //只有这个题目中需要j < m这个条件,因为当==m是就需要输出了。 if(j == -1 || a[i] == b[j]) { i++, j++; } else j = nex[j]; // printf("cbuisdc\n"); } if(j == m) return i - j + 1; return -1; } int main() { scanf("%d", &t); while(t--) { scanf("%d%d", &n, &m); for(int i = 0; i < n; i++) { scanf("%d", &a[i]); } for(int i = 0; i < m; i++) { scanf("%d", &b[i]); } if(kmps() == -1) printf("-1\n"); else printf("%d\n", kmps()); } return 0; }

Period

 HDU - 1358 

从这一题把next数组输出可知,next数组值该字符串的最长相同前缀与后缀,而且是包含了本身的。(如果这么说的话,相当于把字符串下标从1开始)

//这个是没加递归优化的next数组,指的是最长前缀与后缀的值。
//题目意思是指在i之前的字符串是不是有循环节的,有的话就输出循环节的长度与循环节的个数。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxm = 1e6 + 5; int n; int nex[maxm]; void getnext(char ch[], int m) { memset(nex, 0, sizeof(nex)); nex[0] = -1; int j = 0, k = -1; while(j < m) {
  //k表示前缀,j表示后缀。
if(k == -1 || ch[j] == ch[k]) { j++; k++; nex[j] = k; } else k = nex[k]; } //for(int i = 0; i <= m; i++) { // printf("%d \n", nex[i]); //} //printf("\n"); } char ch[maxm]; int main() { int cnt = 0; while(~scanf("%d", &n) && n) { scanf("%s", ch); getnext(ch, n); printf("Test case #%d\n", ++cnt); for(int i = 2; i <= n; i++) { int j = i - nex[i]; if(j != i) { if(i % j == 0) { printf("%d %d\n", i, i / j); } } } printf("\n"); } return 0; }

 Simpsons’ Hidden Talents

 HDU - 2594 

 

https://blog.csdn.net/sunyutian1998/article/details/82960588

扩展kmp板子题

定义母串S,和字串T,设S的长度为n,T的长度为m,求T与S的每一个后缀的最长公共前缀,

也就是说,设extend数组,extend[i]表示T与S[i,n-1]的最长公共前缀,要求出所有extend[i](0<=i<n)。

#include <bits/stdc++.h>
using namespace std;
const int maxn=5e4+10;
 
int nxtt[maxn],nxts[maxn];
//nett是记录模式串的next值,nxts是最终的结果。
int l1,l2; char ch1[maxn],ch2[maxn]; void exkmp(char *t,int lt) { int i=0,j,po; nxtt[0]=lt;//初始化next[0] while(t[i]==t[i+1]&&i+1<lt)//计算next[1] i++; nxtt[1]=i; po=1;//初始化po的位置 for(i=2;i<lt;i++) { if(nxtt[i-po]+i<nxtt[po]+po)//第一种情况,可以直接得到next[i]的值 nxtt[i]=nxtt[i-po]; else//第二种情况,要继续匹配才能得到next[i]的值 { j=nxtt[po]+po-i; if(j<0)j=0;//如果i>po+next[po],则要从头开始匹配 while(i+j<lt&&t[j]==t[j+i])//计算next[i] j++; nxtt[i]=j; po=i;//更新po的位置 } } } void solve(char *s,int ls,char *t,int lt) { int i=0,j,po; while(s[i]==t[i]&&i<lt&&i<ls)//计算ex[0] i++; nxts[0]=i; po=0;//初始化po的位置 for(i=1;i<ls;i++) { if(nxtt[i-po]+i<nxts[po]+po)//第一种情况,直接可以得到ex[i]的值 nxts[i]=nxtt[i-po]; else//第二种情况,要继续匹配才能得到ex[i]的值 { j=nxts[po]+po-i; if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配 while(i+j<ls&&j<lt&&s[j+i]==t[j])//计算ex[i] j++; nxts[i]=j; po=i;//更新po的位置 } } } int main() { int i,ans; while(scanf("%s%s",ch1,ch2)!=EOF) { l1=strlen(ch1),l2=strlen(ch2);
//ch1是模式串,ch2是文本串。 exkmp(ch1,l1); solve(ch2,l2,ch1,l1); ans
=0; for(i=0;i<l2;i++) { if(i+nxts[i]==l2) ans=max(ans,nxts[i]);
//当文本串后缀起始的位置加上模式串匹配的nxts数组的值==文本串的长度,说明匹配。 }
if(ans>0) { for(i=0;i<ans;i++) printf("%c",ch1[i]); printf(" %d\n",ans); } else printf("0\n"); } return 0; }

 

 

 

//思路一把两个串合在一起的做法。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<iostream>
using namespace std;
const int N=5e4+10;
int net[N*2],len;
char a[N],b[N*2];
void getnext()
{
    net[0]=-1;
    int k=-1,j=0;
    while(j<len)
    {
        if(k==-1||b[j]==b[k])
            net[++j]=++k;
        else k=net[k];
    }
}
int main()
{
    while(~scanf("%s%s",b,a))
    {
        int m=strlen(a),n=strlen(b);
        strcat(b,a);
        len=strlen(b);
        getnext();
        if(net[len]*2>len)
        {
            int t=min(m,n);
            for(int i=0;i<t;i++)
                printf("%c",b[i]);
            printf(" %d\n",t);
        }
        else if(net[len]==0) printf("0\n");
        else
        {
            for(int i=0;i<net[len];i++)
                printf("%c",b[i]);
            printf(" %d\n",net[len]);
        }
    }
}

 

hdu(1686)——Oulipo

题意:
给你两个串A,B,让你求A串在B串中的出现次数。

思路:
这个题目有两种做法,第一种就是用扩展kmp做,模板题,在这里我就不讲这种做法了,太简单了!

第二种做法就是用kmp做,利用kmp算法中的next数组来解,我们多求一位next数组,即整个字符串的最长公共前后缀,然后我们进行kmp匹配时,当模式串匹配完成一次后,我们让模式串的那个指针等于多求的那位next数组,继续匹配。具体看代码

//这道题的next数组是由优化的next数组写的,next数组的含义不是指最长前缀与后缀的长度,因为加了递归优化,值一般就是-1与0.
//求最开始的匹配位置。(题目意思)
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxm = 1e6 + 5; int nex[maxm]; int t; char a[maxm], b[maxm]; void getnext() { nex[0] = -1; int k = -1; int j = 0 ; int m = strlen(b); while(j < m) { if(k == -1 || b[j] == b[k]) { j++, k++; if(b[j] != b[k]) nex[j] = k; else nex[j] = nex[k]; } else k = nex[k]; } } int kmpc() { int i = 0, j = 0; int len1 = strlen(a); int len2 = strlen(b); int cnt = 0 ; getnext(); while(i < len1) { if(j == -1 || b[j] == a[i]) { i++, j++; } else j = nex[j]; if(j == len2) cnt++; } return cnt; } int main() { scanf("%d", &t); while(t--) { scanf("%s%s", b, a); memset(nex, 0, sizeof(nex)); printf("%d\n", kmpc()); } return 0; }

 

例题四:
【POJ2752】
题目链接:http://poj.org/problem?id=2752
题意:
给你一个字符串,让你找出这个字符串中所有即是前缀又是后缀的字串的长度

很显然,这个字符串本身就是我们要找的字符串

我们很快可以发现,我们需要找的字符串一定是该字符串的相同的最长前缀和最长后缀的字串

比如说 
ababcababababcabab

满足条件的子串有 ababcabab abab ab

思路:
这就是kmp中next数组的应用,next数组求得是最长的相等前后缀,而这个题目是要你求出所有的相等前后缀,所以我们按照kmp求next数组的方法,当求出next数组后,我们再对字符串最后的位置(str.size()  位置)求一遍next数组,只不过不同的是,当我们验证相等后,不是立即结束,而是寻找其它的更短的满足条件的相等前后缀。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int Next[400005];
char str[400005];
int ans[400005];
int cnt;
int len;

void getNext()
{
    Next[0] = -1;
    int i = 0, j = -1;
    while (i < len)
    {
        if (j == -1 || str[i] == str[j])
        {
            ++i;
            ++j;
            Next[i] = j;
        }
        else j = Next[j];
    }
}

int main()
{
    while(scanf("%s", str)!= EOF) {
        len = strlen(str);
        getNext();
        cnt = 0;
//        printf("%d\n", Next[len]);
        int t = Next[len];
        while(t != 0) {
            if(str[len - 1] == str[t - 1]) ans[cnt++] = t;
            t = Next[t];
        }
        for(int i = cnt - 1; i >= 0; i--) {
            printf("%d ", ans[i]);
        }
        printf("%d\n", len);
    }
    return 0;
}

 https://blog.csdn.net/dyx404514/article/details/41831947 扩展kmp算法

 https://vjudge.net/problem/HDU-4333 题目链接

题意:给你一个数,每次把这个数尾巴上的一个数字放到前面来,问如此循环一遍形成的新的(不重复)数字中,大于,等于,小于原数字的数各有多少个。

比如样例:341->134->413->341,小于、等于、大于的各有1个。

这个串后面接上它本身,作为主串,原串作为模式串。显然这题就是要求出主串每个后缀与模式串的最长公共前缀,直接套扩展KMP模板即可。

因为形成的新的数字必须不重复,因此还需要用KMP的next函数求一下最短循环节。

题解:将两个字符串拼接在一起,然后求出该字符串的循环节。(画一下图九能够看出),如果该点的extends值大于这个长度的话,说明这个值和原始字符串一样的,然后其他的就只要比较I + extend[i]的值,因为其他的相同,而且那个点的值也一定是不同的,要不然extend值更大。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int maxn=100005;
typedef long long ll;
int nxt[maxn];
//获得子串的next数组
void get_next(char str[]){
    int i = 0, j, pos;
    int len = strlen(str);
    nxt[0] = len;
    while(i + 1 < len && str[i] == str[i + 1]){
        ++i;
    }
    nxt[1] = i;
    pos = 1;
    for(i=2; i<len; ++i){
        if(nxt[i - pos] < pos + nxt[pos] - i){
            nxt[i] = nxt[i - pos];
        } else {
            j = nxt[pos] + pos - i;
            if(j < 0){
                j = 0;
            }
            while(i + j < len && str[i + j] == str[j]){
                ++j;
            }
            nxt[i] = j;
            pos = i;
        }
    }
}
//计算extend数组
void EXKMP(char target[], char pattern[], int extend[]){
    int i = 0, j, pos;
    get_next(pattern);
    int len1 = strlen(target);
    int len2 = strlen(pattern);
    while(i < len1 && i < len2 && target[i] == pattern[i]){
        ++i;
    }
    extend[0] = i;
    pos = 0;
    for(i=1; i<len1; ++i){
        if(nxt[i - pos] < pos + extend[pos] - i){
            extend[i] = nxt[i - pos];
        } else {
            j = extend[pos] + pos - i;
            if(j < 0){
                j = 0;//从头匹配
            }
            while(i + j < len1 && j < len2 && target[i + j] == pattern[j]){
                ++j;
            }
            extend[i] = j;
            pos = i;//更新pos
        }
    }
}

void get_next(char b[], int next[]) {
next[0] = -1;
int k = -1;
int j = 0 ;
int m = strlen(b);
while(j < m) {
    if(k == -1 || b[j] == b[k]) {
        j++, k++;
        if(b[j] != b[k]) next[j] = k;
        else next[j] = next[k];
    }
    else k = next[k];

}
//for(int i = 0; i < m; i++) {
//    printf("%d\n", next[i]);
//
//}
}
char s1[maxn * 2], s2[maxn];
int extend[maxn * 2];
int main(){
    int t, cas = 0;
    scanf("%d", &t);
    while(t--){
        scanf("%s", s1);
        int len = strlen(s1);
        for(int i=0; i<len; ++i){
            s1[i + len] = s2[i] = s1[i];
        }
        s1[len + len] = s2[len] = 0;
        get_next(s2, nxt);
        if(nxt[len] != 0 && len % (len - nxt[len]) == 0){
            len = len - nxt[len];
        }//如果存在循环节,就把len更新为循环节长度
        EXKMP(s1, s2, extend); 
      //顺序是用s1的后缀去匹s2的前缀。
int l = 0, e = 0, g = 0; for(int i=0; i<len; ++i){ if(extend[i] >= len){ ++e; } else if(s1[i + extend[i]] > s2[extend[i]]){//只需要比较匹配长度的后面一位即可 ++g; } else { ++l; } } printf("Case %d: %d %d %d\n", ++cas, l, e, g); } return 0; }

 Clairewd’s message

 HDU - 4300 

这一题的意思是要传一段字符串,输入第一行是26个字母对应的字母,第二行是要传字符串,本来这个字符串是前面是加密的,后面是没有加密的,但是现在只截取了前面一部分(保证前面加密的是完整的)

要你输出本来要传的字符串。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int maxn = 100005;
typedef long long ll;
int nxt[maxn];
//获得子串的next数组
void get_next(char str[]){
    int i = 0, j, pos;
    int len = strlen(str);
    nxt[0] = len;
    while(i + 1 < len && str[i] == str[i + 1]){
        ++i;
    }
    nxt[1] = i;
    pos = 1;
    for(i=2; i<len; ++i){
        if(nxt[i - pos] < pos + nxt[pos] - i){
            nxt[i] = nxt[i - pos];
        } else {
            j = nxt[pos] + pos - i;
            if(j < 0){
                j = 0;
            }
            while(i + j < len && str[i + j] == str[j]){
                ++j;
            }
            nxt[i] = j;
            pos = i;
        }
    }
}
//计算extend数组
void EXKMP(char target[], char pattern[], int extend[]){
    int i = 0, j, pos;
    get_next(pattern);
    int len1 = strlen(target);
    int len2 = strlen(pattern);
    while(i < len1 && i < len2 && target[i] == pattern[i]){
        ++i;
    }
    extend[0] = i;
    pos = 0;
    for(i=1; i<len1; ++i){
        if(nxt[i - pos] < pos + extend[pos] - i){
            extend[i] = nxt[i - pos];
        } else {
            j = extend[pos] + pos - i;
            if(j < 0){
                j = 0;//从头匹配
            }
            while(i + j < len1 && j < len2 && target[i + j] == pattern[j]){
                ++j;
            }
            extend[i] = j;
            pos = i;//更新pos
        }
    }
}

//void get_next(char b[], int next[]) {
//next[0] = -1;
//int k = -1;
//int j = 0 ;
//int m = strlen(b);
//while(j < m) {
//    if(k == -1 || b[j] == b[k]) {
//        j++, k++;
//        if(b[j] != b[k]) next[j] = k;
//        else next[j] = next[k];
//    }
//    else k = next[k];
//
//}
////for(int i = 0; i < m; i++) {
////    printf("%d\n", next[i]);
////
////}
//}
char s1[maxn], s2[maxn];
int extend[maxn];
char a[30];
char ch[maxn];
int t;
int main(){
scanf("%d", &t);
while(t--) {
    scanf("%s%s", s1, s2);
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    for(int i = 0; i < len1; i++) {
        a[s1[i] - 'a' ] = i + 'a';
    }
    memset(ch, 0, sizeof(ch));
    for(int i = 0; i < len2; i++) {
        ch[i] = a[s2[i] - 'a'];
    }
    EXKMP(s2, ch, extend);
    int maxx = len2;
    for(int i = 0; i < len2; i++) {
        if(i + extend[i] == len2 && i >= extend[i]) {
//第一个条件是判能不能匹配,第二个条件是判是否满足本来的字符串加密的多余没加密的(题目要求) maxx
= i; break; } } for(int i = 0; i < maxx; i++) { printf("%c", s2[i]); } for(int i = 0; i < maxx; i++) { printf("%c", ch[i]); } printf("\n"); } return 0; }

 

H - Messenger

 CodeForces - 631D 

https://vjudge.net/contest/300092#problem/H

kmp的一道变形题。

 

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
typedef long long ll;
struct node{
    ll len;
    char ch;
}p;
int nxt[maxn];
bool ok(node a, node b){
    if(a.len==b.len&&a.ch==b.ch) return true;
    return false;
}
void cal_next(node *r, int len){
    nxt[0]=-1;
    int j=0, k=-1;
    while(j<len){
        if(k==-1||ok(r[k], r[j])) j++, k++, nxt[j]=k;
        else k=nxt[k];
    }
}
bool judge(node a, node b){
    if(a.ch==b.ch&&a.len>=b.len) return true;
    return false;
}
ll KMP(node *s, int n, node *t, int m, node a, node b){
    cal_next(t, m);
    int i = 1, j=0;
    ll cnt=0;
//    for(i=1; i<n-1; i++){//从第二块开始匹配;
//        while(j&&!ok(s[i], t[j])) j=nxt[j];
//        if(ok(s[i], t[j])) j++;
//        if(j==m){
//            if(judge(s[i+1], b)&&judge(s[i-m], a)) cnt++;//中间块匹配成功后,判断首尾;
//            j=nxt[j];
//        }
//    }
//    for(int i = 0; i <= m; i++) {
//        printf("%d\n", nxt[i]);
//    }
//    printf("%d\n", m);
    while(i < n - 1) {
        if(j == -1 || ok(s[i], t[j])) i++, j++;
        else j = nxt[j];
        if(j == m) {
//            printf("%d %d\n", i, j);
            if(i - m - 1 >= 0 && i <= n - 1 && judge(s[i], b) && judge(s[i - m - 1], a)) {
                cnt++;
//                j = nxt[j];
            }
        }
    }
    return cnt;
}
ll ans1(node *s, int len, node a){//特判只有一块;
    ll cnt=0;
    for(int i=0; i<len; i++){
        if(judge(s[i], a)){
            cnt+=(s[i].len-a.len+1LL);
        }
    }
    return cnt;
}
ll ans2(node *s, int len, node a, node b){//特判只有两块;
    ll cnt=0;
    for(int i=0; i<len-1; i++){
        if(judge(s[i], a)&&judge(s[i+1], b)) cnt++;
    }
    return cnt;
}
int len_s, len_t;
node s[maxn], t[maxn], q[maxn];
int main(){
    int n, m;
    while(~scanf("%d%d", &n, &m)){
        len_s=len_t=0;
        for(int i=0; i<n; i++){
            ll l;
            char r[5];
            scanf("%lld%s", &l, r);
            p.len=l;
            p.ch=r[1];
            if(len_t==0) t[len_t++]=p;
            else{
                if(p.ch==t[len_t-1].ch){
                    t[len_t-1].len+=p.len;
                }
                else t[len_t++]=p;
            }
        }
        for(int i=0; i<m; i++){
            ll l;
            char r[5];
            scanf("%d%s", &l, r);
            p.len=l;
            p.ch=r[1];
            if(len_s==0) s[len_s++]=p;
            else{
                if(p.ch==s[len_s-1].ch){
                    s[len_s-1].len+=p.len;
                }
                else s[len_s++]=p;
            }
        }
        node a, b;
        a=s[0], b=s[len_s-1];//令a, b分别为首尾块;
        int len_q=0;
        for(int i=1; i<len_s-1; i++){
            q[len_q++]=s[i];//把p初始化为中间块;
        }
        if(len_s>2)
            printf("%lld\n", KMP(t, len_t, q, len_q, a, b));
        else{
            if(len_s==1) printf("%lld\n", ans1(t, len_t, a));
            else{
                printf("%lld\n", ans2(t, len_t, a, b));
            }
        }
    }
    return 0;
}

 

posted @ 2019-02-27 00:38  downrainsun  阅读(206)  评论(0编辑  收藏  举报