666 专题三 KMP & 扩展KMP & Manacher

KMP:

Problem A.Number Sequence

d.求子串首次出现在主串中的位置

s.

c.

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 10005//字符串长度

int a[1000005];
int b[MAXN];

int _next[MAXN];

void GetNext(int t[],int M){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    //len=strlen(t);
    len=M;
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int KMPIndex(int s[],int t[],int N,int M){//求子串首次出现在主串中的位置
    int i,j,lens,lent;
    i=j=0;
    //lens=strlen(s);
    //lent=strlen(t);
    lens=N;
    lent=M;

    while(i<lens&&j<lent){
        if(j==-1||s[i]==t[j]){
            ++i;
            ++j;
        }
        else j=_next[j];
    }
    if(j>=lent)return i-lent+1;
    else return -1;
}

int main(){
    int T;
    int N,M;

    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]);
        }

        GetNext(b,M);
        printf("%d\n",KMPIndex(a,b,N,M));
    }
    return 0;
}
View Code

 

Problem B.Oulipo

d.统计子串在主串中的出现次数,可重叠

s.

c.

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 10005//字符串长度

char W[MAXN];
char T[1000005];

int _next[MAXN];

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int KMPCount(char s[],char t[]){//统计子串在主串中的出现次数,可重叠
    int i,j,lens,lent,cnt;
    i=j=0;
    lens=strlen(s);
    lent=strlen(t);
    cnt=0;

    while(i<lens){
        if(j==-1||s[i]==t[j]){
            ++i;
            ++j;
        }
        else j=_next[j];
        if(j==lent)++cnt;
    }
    return cnt;
}

int main(){
    int TT;

    scanf("%d",&TT);

    while(TT--){
        scanf("%s",W);
        scanf("%s",T);

        GetNext(W);
        printf("%d\n",KMPCount(T,W));
    }
    return 0;
}
View Code

 

Problem C.剪花布条

d.统计子串在主串中的出现次数,不可重叠

s.当j==lent时,直接让j=0;而不是j=_next[j];,就不重叠了~

c.

/*
kmp模板
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 1024//字符串长度

int _next[MAXN];

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int KMPCount(char s[],char t[]){//统计子串在主串中的出现次数,不可重叠
    int i,j,lens,lent,cnt;
    i=j=0;
    lens=strlen(s);
    lent=strlen(t);
    cnt=0;

    while(i<lens){
        if(j==-1||s[i]==t[j]){
            ++i;
            ++j;
        }
        else j=_next[j];
        if(j==lent){
            ++cnt;
            j=0;//不可重叠
        }
    }
    return cnt;
}

int main(){
    char str1[MAXN],str2[MAXN];

    while(~scanf("%s",str1)){
        if(str1[0]=='#')break;

        scanf("%s",str2);
        GetNext(str2);
        printf("%d\n",KMPCount(str1,str2));
    }
    return 0;
}
View Code

 

 

Problem D.Cyclic Nacklace

d.给出一串字符串,可以在字符串的开头的结尾添加字符,求添加最少的字符,使这个字符串是循环的(例如:abcab 在结尾添加1个c变为 abcabc 既可)。

s.求出最小循环节,看总长能不能整除。

最小循环节(长度)=len-next[len];

c.

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 100005//字符串长度

char str[MAXN];

int _next[MAXN];

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int main(){

    int T;
    int len;
    int len2;//最小循环节长度

    scanf("%d",&T);

    while(T--){
        scanf("%s",str);

        GetNext(str);

        len=strlen(str);
        len2=len-_next[len];

        if(len2==len){
            printf("%d\n",len2);
        }
        else{
            if(len%len2==0){
                printf("%d\n",0);
            }
            else{
                printf("%d\n",len2-(len%len2));
            }
        }
    }

    return 0;
}
View Code

 

Problem E.Period

d.统计单串中从某个位置以前有多少重复的串(即判断位置i之前的串是不是循环的串。如果是,输出位置i 和循环的次数)

s.最小循环节(长度)=len-next[len];

对每个位置求出最小循环节,然后判断前面的串是不是循环的即可。

c.

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 1000005//字符串长度

char str[MAXN];

int _next[MAXN];

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

void KMPCount2(char t[]){//统计单串中从某个位置以前有多少重复的串
    int i,lent,tmp;
    lent=strlen(t);

    for(i=2;i<=lent;++i){
        tmp=i-_next[i];
        if(i%tmp==0&&i/tmp>1)
            printf("\t位置:%d 个数:%d\n",i,i/tmp);
    }
}

int main(){

    int N;
    int c=0;
    int tmp;//最小循环节长度

    while(~scanf("%d",&N)){

        if(N==0)break;

        scanf("%s",str);

        GetNext(str);

        printf("Test case #%d\n",++c);
        for(int i=2;i<=N;++i){
            tmp=i-_next[i];
            if( (i%tmp==0)&&(i/tmp>1) ){
                printf("%d %d\n",i,i/tmp);
            }
        }
        printf("\n");
    }

    return 0;
}
View Code

 

Problem F.The Minimum Length

d.最小循环节

s.

c.

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 1000005//字符串长度

char str[MAXN];

int _next[MAXN];

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int main(){

    int len;
    int len2;//最小循环节长度

    while(~scanf("%s",str)){
        len=strlen(str);
        GetNext(str);
        len2=len-_next[len];

        printf("%d\n",len2);
    }

    return 0;
}
View Code

 

Problem G.Power Strings

d.求最大的循环次数

s.最小循环节

c.

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 1000005//字符串长度

char str[MAXN];

int _next[MAXN];

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int main(){

    int len;
    int len2;//最小循环节长度

    while(~scanf("%s",str)){
        if(str[0]=='.')break;

        len=strlen(str);
        GetNext(str);
        len2=len-_next[len];

        if(len%len2==0){
            printf("%d\n",len/len2);
        }
        else{
            printf("1\n");
        }
    }

    return 0;
}
View Code

 

Problem H.Seek the Name, Seek the Fame

d.给出一个字符串str,求出str中存在多少子串,使得这些子串既是str的前缀,又是str的后缀。从小到大依次输出这些子串的长度。

s.所以对于这道题,求出len处的next值,并递归的向下求出所有的next值,得到的就是答案。

c.

/*
kmp模板
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 400005//字符串长度

int _next[MAXN];

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int main(){

    char str[MAXN];

    int sum[MAXN];
    int k,len;

    while(~scanf("%s",str)){
        GetNext(str);

        k=0;
        len=strlen(str);
        for(int i=len;_next[i]>0;i=_next[i]){
            sum[k++]=_next[i];
        }

        for(int i=k-1;i>=0;--i){
            printf("%d ",sum[i]);
        }
        printf("%d\n",len);
    }

    return 0;
}
View Code

 

Problem I.Blue Jeans

d.求 N 个字符串的最长连续公共子串,N 范围是 10 ,每个串最长 60,所以可以暴力……

本来是没什么意思的,不过可以学习下string的几个函数

s.暴力。好像还能用后缀数组做,以后再看。

c.

#include<iostream>
#include<stdio.h>
#include<string>
using namespace std;

/*
涉及到string类的两个函数find和substr:
1、find函数
原型:size_t find ( const string& str, size_t pos = 0 ) const;
功能:查找子字符串第一次出现的位置。
参数说明:str为子字符串,pos为初始查找位置。
返回值:找到的话返回第一次出现的位置,否则返回string::npos

2、substr函数
原型:string substr ( size_t pos = 0, size_t n = npos ) const;
功能:获得子字符串。
参数说明:pos为起始位置(默认为0),n为结束位置(默认为npos)
返回值:子字符串
*/

int main(){

    int n;
    int m;
    string s[10];

    scanf("%d",&n);

    while(n--){
        scanf("%d",&m);
        for(int i=0;i<m;++i){
            cin>>s[i];
        }

        string ans="";
        for(int i=60;i>=3;--i){//长度>=3
            for(int j=0;j<=60-i;++j){
                string tmp=s[0].substr(j,i);//从j开始取i长度的子串

                bool flag=true;
                for(int k=1;k<m;++k){
                    if(s[k].find(tmp)==string::npos){
                        flag=false;
                        break;
                    }
                }
                if(flag){
                    if(ans==""){
                        ans=tmp;
                    }
                    else if(tmp<ans){
                        ans=tmp;
                    }
                }
            }
            if(ans!="")break;
        }

        if(ans==""){
            printf("no significant commonalities\n");
        }
        else{
            cout<<ans<<endl;
        }
    }

    return 0;
}
View Code

 

Problem J.Simpsons’ Hidden Talents

d.两个字符串s1、s2,求s1和s2的最长的相同的s1前缀和s2后缀

s.先求s1的next数组,再求s2的next数组(即代码中_next2数组,此时不是自己与自己匹配,而是与s1匹配),最后看_next2[len2]即可(len2为串s2的长度)。

c.

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 50005//字符串长度

char s1[MAXN];
char s2[MAXN];

int _next[MAXN];//s1的next数组,s1与自己匹配
int _next2[MAXN];//s2的next数组,s2与s1匹配

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

void GetNext2(char s[],char t[]){//s的前部与t匹配,求t相对于s的next数组(_next2[])
    int j,k,len;
    //注意这几个初始化与上面不同
    j=0;//从0开始,首先求_next[1]
    k=0;//比较指针
    _next2[0]=0;//初始值0
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==s[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next2[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int main(){

    while(~scanf("%s%s",s1,s2)){

        GetNext(s1);
        GetNext2(s1,s2);

        int len2=strlen(s2);
        if(_next2[len2]==0){
            printf("0\n");
        }
        else{
            for(int i=0;i<_next2[len2];++i){
                printf("%c",s1[i]);
            }
            printf(" %d\n",_next2[len2]);
        }
    }

    return 0;
}
View Code

 

Problem K.Count the string

d.统计所有前缀在串中出现的次数和

s.next数组,递推

c.

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 200005//字符串长度
#define MOD 10007

char s[MAXN];
int dp[MAXN];

int _next[MAXN];

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int main(){

    int T;
    int n;
    int len;

    int ans;

    scanf("%d",&T);

    while(T--){
        scanf("%d",&n);
        scanf("%s",s);

        GetNext(s);
        len=strlen(s);

        ans=0;
        dp[0]=0;

        for(int i=1;i<=len;++i){
            dp[i]=dp[_next[i]]+1;
            dp[i]%=MOD;
            ans+=dp[i];
            ans%=MOD;
        }

        printf("%d\n",ans);
    }

    return 0;
}
View Code

 

Problem L.Clairewd’s message(此题再看看,未完成)

d.

给出26个英文字母的加密表,明文中的'a'会转为加密表中的第一个字母,'b'转为第二个,...依次类推。

然后第二行是一个字符串(str1),形式是密文+明文,其中密文一定完整,而明文可能不完整(也可能没有)。

求出最短的完整的字符串(密文+明文)。

s.

1.用kmp来做:

首先肯定的是,给定的串中明文长度一定小于等于密文。也就是说明文长度小于等于总长的一半。

于是,取总长的后一半作为主串,然后把串反翻译一遍得到str2,然后用str2与str1的后一半进行匹配。首次把str1的后一半匹配完的位置即是给定的串中明文开始的位置。

因为是首次,所以保证了前面的密文长度最小,即总长度最小。

然后输出密文+明文,即可。

2.用扩展kmp来做:

//next[i]:x[i...m-1]与x[0...m-1]的最长公公前缀

//extend[i]:y[i...n-1]与x[0...m-1]的最长公共前缀

可以用extend数组,根据它的意义,str1作为y,str2作为x,当 i+extend[i]==len1时代表y中的从i到结尾均与x的开头匹配,如果此时i大于串长度的一半,则满足条件。

此时的i即为实际密文(明文)的长度,然后按要求输出答案即可。

c.kmp来做

/*
kmp模板
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 100005//字符串长度

int _next[MAXN];

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int KMPIndex(char s[],char t[]){//s的后缀等于t的前缀的最大长度
    int i,j,lens,lent;
    i=j=0;
    lens=strlen(s);
    lent=strlen(t);

    while(i<lens&&j<lent){
        if(j==-1||s[i]==t[j]){
            ++i;
            ++j;
        }
        else j=_next[j];
    }
    //if(j>=lent)return i-lent;
    //else return -1;
    return j;
}

int main(){

    char str[26],str1[MAXN],str2[MAXN];
    char cstr[26];//密文->明文
    int T;

    scanf("%d",&T);

    while(T--){
        scanf("%s",str);
        scanf("%s",str1);

        for(int i=0;i<26;++i){//密文->明文
            cstr[str[i]-'a']='a'+i;
        }

        int len1=strlen(str1);

        for(int i=0;i<len1;++i){
            str2[i]=cstr[str1[i]-'a'];
        }
        str2[len1]='\0';

        GetNext(str2);

        int len11=len1/2;//假设串中明文长度(即串中明文最大长度)

        int num=KMPIndex(str1+len1-len11,str2);//串中实际明文长度

        printf("%s",str1);
        len11=len1-num;//实际密文(明文)长度
        for(int i=num;i<len11;++i){
            printf("%c",str2[i]);
        }
        printf("\n");
    }

    return 0;
}
View Code

c2.扩展kmp(略)

 

Problem M.Substrings

d.找出所有串的最长的公共连续子串(逆序相同也可以)

s.直接从最小的那串,枚举所有子串去寻找。反正最多100串,最长100字符。

c.

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

int main(){

    int t;
    int n;
    char str[105][105];
    char subs1[105],subs2[105];//子串

    int mi,lmi;
    int tmp;
    int len;
    bool flag;
    int ans;

    int len2;

    scanf("%d",&t);

    while(t--){
        scanf("%d",&n);

        mi=105;//最小长度
        lmi=0;//最小长度的下标

        for(int i=0;i<n;++i){
            scanf("%s",str[i]);
            tmp=strlen(str[i]);
            if(tmp<mi){
                mi=tmp;
                lmi=i;
            }
        }

        len=strlen(str[lmi]);//最小的长度
        ans=0;

        for(int i=0;i<len;++i){//子串头
            for(int j=i;j<len;++j){//子串尾

                for(int k=i;k<=j;++k){
                    subs1[k-i]=str[lmi][k];//正序
                    subs2[j-k]=str[lmi][k];//逆序
                }
                subs1[j-i+1]='\0';
                subs2[j-i+1]='\0';

                len2=strlen(subs1);
                flag=true;
                for(int k=0;k<n;++k){
                    if(!strstr(str[k],subs1)&&!strstr(str[k],subs2)){
                        flag=false;
                        break;
                    }
                }

                if(flag&&len2>ans){
                    ans=len2;
                }
            }
        }

        printf("%d\n",ans);

    }

    return 0;
}
View Code

 

 

Problem Z.Theme Section

d.在一个串中找 EAEBE 的形式的最长的E,其中E为一个字符串,也就是说找到前缀与后缀相同,并且串中还存在相同的一段,它们不能重复。

s.利用next数组,next[len]代表的即是最大的相同的前缀与后缀,然后让 i 从len-1往前遍历找到 i>=2(前面部分最少要有2个字符),在过程中更新最长的长度ans即可。

c.

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

#define MAXN 1000005//字符串长度

char str[MAXN];

int _next[MAXN];

void GetNext(char t[]){//求next数组
    int j,k,len;
    j=0;//从0开始,首先求_next[1]
    k=-1;//比较指针
    _next[0]=-1;//初始值-1
    len=strlen(t);
    while(j<len){
        if(k==-1||t[j]==t[k]){//指针到头了,或者相等
            ++j;
            ++k;
            _next[j]=k;//此句可由优化替代
            /*优化(求匹配位置时可用)
            if(t[j]!=t[k])_next[j]=k;
            else _next[j]=_next[k];
            //*/
        }
        else k=_next[k];
    }
}

int main(){

    int N;
    int len;
    int m,k,ans;//m代表最大的首尾相同长度,k为i之前与开头重复的长度

    scanf("%d",&N);

    while(N--){
        scanf("%s",str);
        GetNext(str);

        len=strlen(str);
        m=_next[len];//m代表最大的首尾相同长度
        ans=0;
        for(int i=len-1;i>=2;--i){
            k=_next[i];//k为i之前与开头重复的长度
            while(k>0){
                if(k<=m&&k+k<=i&&i+k<=len){//长度小于m,且三段不重合
                    if(k>ans)ans=k;
                    break;//当前是最大的长度
                }
                k=_next[k];//next[k]一定小于k
            }
        }
        printf("%d\n",ans);
    }

    return 0;
}
View Code

 

 

扩展KMP:

 

Manacher:

 

posted @ 2016-01-18 15:50  gongpixin  阅读(235)  评论(0编辑  收藏  举报