kmp总结

因为kmp学的时间有点早, 所以这也是我一直有些含糊的算法之一吧

matrix67这篇很经典的kmp的讲解也不知道看过多少遍了。。。 对了今天看到了这个关于matrix67和他老婆的故事真的觉得很感动。。(一下子浪费了半个小时T T)简直是让人相信爱情了T T。。 还有超有爱的这个

呜呜呜太感人了                       

言归正传, 先上几道裸题吧


poj 3461

纯裸题, kmp有一个易错点是它两次循环的初始值是不一样的! 字串中因为是要和它的前缀匹配所以是从1开始扫 而 主串中 因为是要和字串进行匹配所以就从0开始!

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
char a[10005], b[1000005];
int next[10005];
int main()
{
    int t; scanf("%d", &t); while(t --){
        scanf("%s%s",  a, b);
        int lena = strlen(a), lenb = strlen(b), ans = 0;
        int j = -1; next[0] = -1;
        for(int i = 1; i < lena; i ++){
            while(j >= 0 && a[j + 1] != a[i])j = next[j];
            if(a[j + 1] == a[i])j ++;
            next[i] = j;
        }   j = -1; 
        for(int i = 0; i < lenb; i ++){
            while(j >= 0 && a[j + 1] != b[i])j = next[j];
            if(a[j + 1] == b[i])j ++;
            if(j == lena - 1){
                ans ++; j = next[j];    
            }    
        }cout<<ans<<endl;
    }
    //system("pause");
    return 0;
}

poj 2406

这看起来真的是蛮神奇的一件事:

一个有循环节 k 的串  它从第k + 1 位到第 n 位的next数组是单调的!

其实仔细想想这还是挺显然的, 但是如果只是让我自己想的话肯定不会吧这个和kmp联系起来。。。

简单的证明:

假设我们有一个字符串ababab,那么next[6]=4对吧,由于next的性质是,匹配失败后,下一个能继续进行匹配的位置,也就是说,把字符串的前四个字母,abab,平移2个单位,这个abab一定与原串的abab重合(否则就不满足失败函数的性质),这说明了什么呢,由于字符串进行了整体平移,而平移后又要重叠,那么必有
s[1]=s[3],s[2]=s[4],s[3]=s[5],s[4]=s[6].说明长度为2的字符串在原串中一定重复出现,

代码太短了。。


poj 2752

是真的每一题都太机智了还是我太傻了啊。。。

论next 数组的100中用途。。

因为我们知道 lens 一定是一个可行解, 而其余的解就是它既是  它的开头又是它的结尾 , 而其中最大的就是next[lensn], 所以就很好求出全部的解了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define N 1000005
using namespace std;
int next[N];
char s[N];
void work(int x){
    if(x == -1)return;
    work(next[x]);
    printf("%d ", x + 1);    
}
int main()
{
    while(scanf("%s", s) != EOF ){
        int j = -1; next[0] = -1;
        int lens = strlen(s);
        for(int i = 1; i < lens; i ++){
            while(j >= 0 && s[j + 1] != s[i])j = next[j];
            if(s[j + 1] == s[i])j ++;
            next[i] = j;    
        }   
     //   for(int i = 0; i < lens; i ++)printf("%d ", next[i]);cout<<endl;
        work(lens - 1);cout<<endl;
    }//system("pause");
    return 0;
}

hdu 4763

很简单的题, 还是找出next后然后找规律, 把中间的数扫一遍就行了

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define N 1000005
using namespace std;
char s[N];
int next[N], lens;
bool pd(int x){
    for(int i = x * 2 - 1; i < lens - x; i ++)
        if(next[i] >= x - 1)return 1; return 0;    
}
int main()
{
    int t; scanf("%d", &t); while(t --){
        scanf("%s", s);
        int j = -1; next[0] = -1; lens = strlen(s);
        for(int i = 1; i < lens; i ++){
            while(j >= 0 && s[j + 1] != s[i])j = next[j];
            if(s[j + 1] == s[i])j ++;
            next[i] = j;    
        }    
        int ans = 0;
        for(int i = next[lens - 1] + 1; i > 0; i --)
            if(pd(i)){ans = i; break;}  
        cout<<ans<<endl;
    }
   // system("pause");
    return 0;
}



poj 2185

就是把每一行每一列长和宽 分别求出来 循环的序列数, 然后分别算出来长和宽 需要的数量 乘起来 就好了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define si s[i]
using namespace std;
int r, c, next[10005];
char s[10005][80];
int gcd(int a, int b){return a ? gcd(b % a, a) : b; }
int xgb(int a, int b){return a * b / gcd(a, b);}
int main()
{
    scanf("%d%d", &r, &c);
    int tmpx = 1, tmpy = 1;
    for(int i = 0; i < r; i ++){
        scanf("%s", s[i]);
        int j = -1; next[0] = -1;
        for(int k = 1; k < c; k ++){
            while(j >= 0 && si[j + 1] != si[k])j = next[j];
            if(si[j + 1] == si[k])j ++;
            next[k] = j;    
        }
        tmpx = xgb(tmpx, c - next[c - 1] - 1); 
    }
    if(tmpx > c) tmpx = c;
    for(int i = 0; i < c; i ++){
        int j = -1;
        for(int k = 1; k < r; k ++){
            while(j >= 0 && s[k][i] != s[j + 1][i])j = next[j];
            if(s[j + 1][i] == s[k][i])j ++;
            next[k] = j;
        }
        tmpy = xgb(tmpy, r - next[r - 1] - 1);
    }
    if(tmpy > r)tmpy = r;
    cout<<tmpx * tmpy<<endl;
   // system("pause");
    return 0;
}



这里  算是一个kmp 的应用吧, 单看这个结论真的是太神奇了, 但是想到证明的原因就会觉得很显然了。

连续抛一枚硬币抛出长为L的不同排列的几率是不同的, 而不是我们以为的 1/ (2 ^ L)。 每次试配的时候 重新 匹配的起始节点应该是 这个点的取反(也就是这次掷出来的点)与 需要匹配出来的串 的前缀。


基础的kmp就到这里吧, 下面是拓展kmp

扩展kmp  讲得很好了, 字符串这种东西果然是要用图才能讲明白的

现在这个代码是有错的

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
char a[55], b[55];
int next[55], ext[55];
int main()
{
    scanf("%s%s", a, b);
    int lena = strlen(a), j = 0;
    while(j + 1 < lena && a[j] == a[j + 1])j ++;
    next[1] = j;
    int k = 1, p = j - 1;        // 当前最远访问到的位置 
    for(int i = 2; i < lena; i ++){
        int l = next[i - k];
        if(i + l <= p) {next[i] = l; continue;}
        int j = p - i + 1;  if(j < 0)j = 0;
        while(i + j < lena && a[i + j] == a[j])j ++;
        next[i] = j; k = i; p = j - 1;
    } 
    for(int i = 0; i < lena; i ++)printf("%d ", next[i]);cout<<endl;
    int lenb = strlen(b); j = 0;
    while(j < lenb && j < lena && a[j] == b[j])j ++;
    ext[0] = j;
    k = 0;p = j - 1;
    for(int i = 1; i < lena; i ++){
        int l = next[i - k];
        if(i + l <= p){ext[i] = l; continue;}    
        int j = p - i + 1; if(j < 0)j = 0;
        while(i + j < lena && b[j] == a[i + 1])j ++;
        ext[i] = j; k = i; p = i + j - 1;
        printf("%d  %d  %d %d  %d\n", i, l, p, ext[i], j);
    }
    for(int i = 0; i < lena; i ++)printf("%d ", ext[i]);cout<<endl;
    system("pause");
    return 0;
}


vj 1866 动物园

我写的这种方法代码乍一看简直就是一个纯kmp, 事实上也差不多, 就是求出next数组后, 下一遍还是扫它自身, 但是这时的j 一直保证比 i / 2 小就可以了。^_^    > _ <

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define MAXN 1100005
#define mod 1000000007
using namespace std;
char s[MAXN];
int next[MAXN], lala[MAXN];
long long ans = 1;
int main()
{
    int t; scanf("%d", &t); while(t --){
        scanf("%s", s + 1); int j = 1, lens = strlen(s + 1); ans = 1;
        next[1] = 0; j = 0; lala[1] = 1;
        for(int i = 2; i <= lens; i ++){
            while(j && s[i] != s[j + 1])j = next[j];
            if(s[i] == s[j + 1])j ++;
            next[i] = j; 
            lala[i] = lala[j] + 1;
        }    j = 0;
        for(int i = 2; i <= lens; i ++){
            while(j && s[i] != s[j + 1])j = next[j];
            if(s[i] == s[j + 1])j ++;    
            if(j * 2 > i)j = next[j];
            ans = (ans * (lala[j] + 1)) % mod;
        }
        cout<<ans<<endl;
    }
  //  system("pause");
    return 0;
}


hdu 1841

找出最小的同时包含两个串的串。

显然 , 最后的结果只包含这几种情况: 1的后缀接2的前缀, 2的后缀接1的前缀, 1包含在2中, 2包含在1中, 1和2首尾相连。

第一次忘记了包含这种情况~~~~(>_<)~~~~

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <time.h>
#include <queue>
#define MAXN 1000005
using namespace std;
char s1[MAXN], s2[MAXN];
int next[MAXN], ans;
inline void getnext(char s[]){
	next[0] = -1;
	int j = -1, len = strlen(s);
	for(int i = 1; i < len; i ++){
		while(j >= 0 && s[j + 1] != s[i])j = next[j];
		if(s[j + 1] == s[i]) j ++;
		next[i] = j;
	}
}
inline int pei(char s[], char s2[]){
	int j = -1, len = strlen(s), len2 = strlen(s2);
	for(int i = 0; i < len; i ++){
		while(j >= 0 && s2[j + 1] != s[i])j = next[j];
		if(s2[j + 1] == s[i]) j ++;
		if(j  + 1 == len2)return -2;
	}return j;
}
int main()
{
	int t; scanf("%d", &t);
	while(t --){
		scanf("%s%s", s1, s2); ans = -1;
		int l1 = strlen(s1), l2 = strlen(s2);
		getnext(s1); int a = pei(s2, s1); if(a == -2){cout<<l2<<endl; continue;}
		getnext(s2); int b = pei(s1, s2); if(b == -2){cout<<l1<<endl; continue;}
		if(a == -1 && b == -1)cout<<l1 + l2<<endl;
		else cout<<l1 + l2 - max(a, b) - 1<<endl;
	} 
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值