关于KMP的一点思考

关于KMP的一点思考

KMP的\(next\)数组的性质很精妙,有必要开一个坑学习一下

Part 1 啥是next

\(next[i]\)表示对于\(pre_i\)这个字符串,这个抠出来的字符串本身后缀和前缀相等的最长长度。是一个自变量只和这个子串有关的函数。这点很重要

由于保证了是最长长度,这个数有一些优良的性质,常常在关于一个串的循环表示或者周期表示中发挥作用。

注意到这个\(next[i]\)虽然代表是这个最长长度,但是值得注意的是,由于字符串从1开始编号,所以这个值也是那个前缀的下标。

Part2 如何求next

边界条件是,\(nx[1]=0\)。考虑我们若已经求得前面\(i-1\)的位置的\(nx\)值,现在如何求\(nx[i]\)。

把\(S[1\dots i-1]\)看做一个整体,现在我们在后面加入了一个字符\(S[i]=c\)。

我们现在就是要在\(pre_{nx[i-1]}\)中截一个最大的位置\(p\),使得\(S[p+1]=c\),而\(p\)虽然是下标,但是由于从\(1\)开始编号那么就同时就是这个串的长度,所以\(nx[i]=p+1\)。为什么是在\(pre_{nx[i-1]}\)中找呢?因为我们要保证\(S[i-p+1...i]=S[1,p]\)。

所以如何找\(p\)呢?由于我们要保证刚刚写的这个等式,可以发现\(p\)一定是在\(G=(V,E),E=(x,nx[x])\)这样的图中和\(nx[i-1]\)联通的到祖先的链上,所以我们一直暴力跳\(nx[]\)也就是遍历这条链,直到第一次找到一个位置\(p\)使得\(S[nx[p]+1]=S[i]\)。

但是你可能觉得这样的复杂度是假的,下面我将证(复)明(读)暴力跳\(nx[]\)遍历的复杂度不超过\(O(n)\)

可以发现\(nx[i]\le nx[i-1]+1\),得证。

哈哈哈哈

其实就是,\(nx[i]\)的总增长是\(O(n)\)的,而且一次最多增长\(1\),所以在其间不断跳的复杂度不超过\(O(n)\)。(总共只有这么多\(nx\)给你跳啊!)

代码:

    for(int t=2;t<=s2;++t){
        nx[t]=nx[t-1];
        while(nx[t]>0&&T[nx[t]+1]!=T[t]) nx[t]=nx[nx[t]];
        if(T[nx[t]+1]==T[t]) ++nx[t];
    }

Part 3 一些性质

由于我马上就要咕咕咕所以

P3435 [POI2006]OKR-Periods of Words

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define getchar() (__c==__ed?(__ed=__buf+fread(__c=__buf,1,1<<18,stdin),*__c++):*__c++)

using namespace std;  typedef long long ll;   char __buf[1<<18],*__c=__buf,*__ed=__buf;
inline int qr(){
    register int ret=0,f=0;
    register char c=getchar();
    while(!isdigit(c))f|=c==45,c=getchar();
    while(isdigit(c)) ret=ret*10+c-48,c=getchar();
    return f?-ret:ret;
}
const int maxn=1e6+5;
char c[maxn];
int n,nx[maxn],cut[maxn];

inline void kmp(){
    for(int t=2;t<=n;++t){
        nx[t]=nx[t-1];
        while(nx[t]>0&&c[nx[t]+1]!=c[t]) nx[t]=nx[nx[t]];
        if(c[nx[t]+1]==c[t]) ++nx[t];
    }
}

int Find(const int&p){
    if(!nx[p]) return p;
    if(cut[p]) return cut[p];
    return cut[p]=Find(nx[p]);
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.in","r",stdin);
    //freopen("out.out","w",stdout);
#endif
    scanf("%d%s",&n,c+1);
    kmp();
    ll ans=0;
    for(int t=1;t<=n;++t)
        ans=(ans+t-Find(t));
    printf("%lld\n",ans);
    return 0;
}

[P4824 USACO15FEB]Censoring (Silver) 审查(银)

//@winlere
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;  typedef long long ll;
const int maxn=1e6+5;
char S[maxn],T[maxn];
pair<int,int> stk[maxn];
int s1,s2,top,nx[maxn];

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.in","r",stdin);
    freopen("out.out","w",stdout);
#endif
    scanf("%s%s",S+1,T+1);
    s1=strlen(S+1);
    s2=strlen(T+1);
    for(int t=2;t<=s2;++t){
        nx[t]=nx[t-1];
        while(nx[t]>0&&T[nx[t]+1]!=T[t]) nx[t]=nx[nx[t]];
        if(T[nx[t]+1]==T[t]) ++nx[t];
    }
    int p=0;
    for(int t=1;t<=s1;++t){
        while(p&&T[p+1]!=S[t]) p=nx[p];
        if(T[p+1]==S[t]) ++p;
        stk[++top]=(pair<int,int>){t,p};
        if(p==s2) top-=s2,p=stk[top].second;
    }
    for(int t=1;t<=top;++t) printf("%c",S[stk[t].first]);
    putchar('\n');
    return 0;
}

原文地址:https://www.cnblogs.com/winlere/p/11847972.html

时间: 2024-10-30 10:06:23

关于KMP的一点思考的相关文章

关于后台系统自动生成的一点思考

大量实践发现后台管理程序,其实90%的代码都是相同的,当然是在抛弃复杂逻辑业务的情况下,那么如何能高效的节约这些时间呢,那就是接下来我要说的,对于后台系统自动生成的一些思考. 适用情景: 1.表编号id为自增(基于现在大部分表编号都是自增的情况): 2.没有太复杂业务关联关系,比如表的某一个字段,存储了一个json对象,为了平衡后台用户使用,需要友好的分段展示给用户的定制ui界面:还比如表中存储了外键的多个id,但为了方便用户使用,只能已标签name的方式,给用户展示,等等这些超强业务黏合逻辑的

关于前端的一点思考

关于前端的一点思考 Author:tkorays 最近写前端代码,写着写着就突然开始惆怅.忧伤.愤怒.发狂,我TMD到底在干什么啊! 很多东西写了n遍了,但是还是在不停地写着.自己写过的代码也不想再修改完善.重新利用,只是觉得,可能重新写一遍可能要好点.面对这很多库以及框架,虽然喜爱,但是也是有所顾忌,我只要使用其中的一个功能,根本不需要引入这么大的整个库. 事实上,我们可能在动手写任何代码之前,先要思考下,我们到底要的是什么! 0x00 界面真的需要这么炫酷么 在使用某个界面库之前,我们可能先

关于Emit中动态类型TypeBuilder创建类标记的一点思考

  利用TypeBuilder是可以动态创建一个类型,现在有个需求,动态生成一个dll,创建类型EmployeeEx,需要继承原dll里面的Employee类,并包含Employee类上的所有类标记.   网上有很多例子, //创建TypeBuilder. TypeBuilder myTypeBuilder = myModBuilder.DefineType(typeName, TypeAttributes.Public); myTypeBuilder.SetParent(type);   大概

关于失败的一点思考

睡觉之前突然想到马云说过的一句话:我们要习惯于拒绝,习惯失败,如果我们还没成功,那是因为我们的失败还不够 --------2016.4,11  以此自勉 关于失败的一点思考

有关盒模型的一点思考

有关盒模型的一点思考 盒子模型是css中一个重要的概念,理解了盒子模型才能更好的排版. 其实盒子模型有两种,分别是标准 w3c 盒子模型和 IE 盒子模型. 他们对盒子模型的解释各不相同,先来看看我们熟知的标准盒子模型: 一.w3c盒子模型 看下面的图,根据色块,右外倒内,分别代表margin.border.padding.content(即网页内容部分) 二.IE盒子模型 与w3c盒子模型的组成部分类似,IE盒子模型也包括上图几个部分 但是不同的是,IE盒子模型把border和padding归

关于模板方法和策略模式的一点思考

该随笔的思想原点,应该算是在两三年前了.当时和一前同事聊天.不知怎得就聊到了Http访问. 一.我记得他和我说过的第一句话,大概是:有没有已经封装好的.比较强大的HttpUtil.也可能是受业务的影响(接口对内).我当时接触到的Http访问,大多比较“规范”,至少有一个接口约束在约定着某些东西,不至于一会传递json,返回json, 一会又要传递xml,返回xml,甚至更奇葩的是,上传个文件.返回0或者1.如果真出现这样的状态,HttpUtil依然能够方便.灵活的适应着各种情况.我想这个Util

关于android SDK安装Failed to fetch URL 一点思考

最近SDK出问题了,然后在google下载了一个android-sdk-windows.rar,然后点击SDK Manager,结果一直不能刷新API Level,然后就开始在网上找了好多资料,解决这个问题,修改 HOSTS,    HTTP  和  HTTPS  都不能解决,这给我带来了很大的困惑!   加载不出来的界面错误为: Fetching http://dl-ssl.google.com/android/repository/addons_list-1.xml Failed to fe

JavaScript组合继承的一点思考

今天看<JavaScript高级程序设计>一书中关于组合继承模式时,书上有这么一个Demo程序: <html> <head> </head> <body> <script> function SuperType(name){ this.name = name; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name,

关于工作习惯的一点思考

最近项目发布新的版本,一个月要求四个人完工上线.我负责实现接口和相应的数据处理,从整体的任务比重上看能站到20%左右.我平时做事情比较赶,也就是属于拿的活差不多有个大体了解,就开始干,到功能实现为止.所谓的功能实现,就是能拿到相应的数据,至于数据整不正确,我一点兴趣都不感.所以整个项目下来,当别人在忙着写前端实现的时候,我就开始闲了,能到别人去调我的方法的时候,才发现我的方法,这里少个判断,那里数据错位... 昨天客户要求在下班前发布新版本,并且把老版本的用户数据同步到新版本上,由于数据结构做了