• KMP && Manacher && 扩展KMP整理


    KMP算法:

    kmp示例代码:

    void cal_next(char *str, int *next, int len)
    {
        next[0] = -1;//next[0]初始化为-1,-1表示不存在相同的最大前缀和最大后缀
        int k = -1;//k初始化为-1
        for (int q = 1; q <= len-1; q++)
        {
            while (k > -1 && str[k + 1] != str[q])//如果下一个不同,那么k就变成next[k],注意next[k]是小于k的,无论k取任何值。
            {
                k = next[k];//往前回溯
            }
            if (str[k + 1] == str[q])//如果相同,k++
            {
                k = k + 1;
            }
            next[q] = k;//这个是把算的k的值(就是相同的最大前缀和最大后缀长)赋给next[q]
        }
    }
    int KMP(char *str, int slen, char *ptr, int plen)
    {
        int *next = new int[plen];
        cal_next(ptr, next, plen);//计算next数组
        int k = -1;
        for (int i = 0; i < slen; i++)
        {
            while (k >-1&& ptr[k + 1] != str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配)
                k = next[k];//往前回溯
            if (ptr[k + 1] == str[i])
                k = k + 1;
            if (k == plen-1)//说明k移动到ptr的最末端
            {
                //cout << "在位置" << i-plen+1<< endl;
                //k = -1;//重新初始化,寻找下一个
                //i = i - plen + 1;//i定位到该位置,外层for循环i++可以继续找下一个(这里默认存在两个匹配字符串可以部分重叠),感谢评论中同学指出错误。
                return i-plen+1;//返回相应的位置
            }
        }
        return -1;  
    }
        char *str = "bacbababadababacambabacaddababacasdsd";
        char *ptr = "ababaca";
        int a = KMP(str, 36, ptr, 7);
        return 0;

    kmp算法是用来找模式串是否在主串中出现,并返回第一次出现的位置。(模式串一般都比主串长度短,求的是模式串在主串中是否出现)

    它有一个数组next[len](len是ptr字符串的长度),next[i]这里面放的是模式串的前i个字符的最长公共前后缀。(前缀不包括第i个字符)

    时间复杂度:O(n+m)

    算法过程:

    你求的是模式串在主串中出现的位置,next[i]数组是模式串前i个字符的最长公共前后缀。
    S1=abadfgag 
    S2=abac
    next[0]=-1
    next[1]=-1
    next[2]=1
    next[3]=-1
    第一次匹配从S1第0位开始,得到S1[0,2]==S2[0,2]
    那么下一次匹配还要从S1第1位开始吗?
    不需要。kmp优化的就是这里,因为next[2]=1
    那么就有S2[0]=S2[2],又因为S1[0,2]==S2[0,2]
    所以S1[0]==S1[2],那么我们这次就可以直接从S1第3位开始匹配

    具体过程点这里

    例题:传送门

    题意:

    t组输入,给你一个模式串ptr,让你找出来它在str中出现的次数

    题解:

    利用KMP算法,只需要在kmp原来的基础上稍微改一下就可以用来求次数

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<iostream>
     4 #include<algorithm>
     5 using namespace std;
     6 const int maxn=1000005;
     7 const int INF=0x3f3f3f3f;
     8 char str[maxn],ptr[maxn];
     9 int ans;
    10 void get_next(int len,int *next)
    11 {
    12     next[0]=-1;
    13     int k=-1;
    14     for(int i=1;i<=len-1;++i)
    15     {
    16         while(k>-1 && ptr[k+1]!=ptr[i])
    17             k=next[k];
    18         if(ptr[k+1]==ptr[i])
    19         {
    20             k+=1;
    21         }
    22         next[i]=k;
    23     }
    24 }
    25 void kmp(int slen,int plen)
    26 {
    27     int next[plen]; //这个next数组长度只能定义plen个长度,大的话会出错 
    28     get_next(plen,next);
    29     int k=-1;
    30     for(int i=0;i<slen;++i)
    31     {
    32         while(k>-1 && ptr[k+1]!=str[i])
    33         {
    34             k=next[k];
    35         }
    36         if(ptr[k+1]==str[i])
    37         {
    38             k=k+1;
    39         }
    40         if(k==plen-1)
    41         {
    42             ans++;
    43             k=next[k];
    44             //i=i-plen+1+1;
    45         }
    46     }
    47 }
    48 int main()
    49 {
    50     int t;
    51     scanf("%d",&t);
    52     while(t--)
    53     {
    54         ans=0;
    55         scanf("%s%s",ptr,str);
    56         int slen=strlen(str);
    57         int plen=strlen(ptr);
    58         kmp(slen,plen);
    59         printf("%d
    ",ans);
    60     }
    61     return 0;
    62 }
    View Code

    有些题目利用next数组的特性来做,比如G - Seek the Name, Seek the Fame

    这道题目需要找出来模式串中前后缀的所有相同长度,而不只是求最长

    比如“alala”,它的最长相同前后缀是“ala”,但是还有“a”。等到把所有前后缀相同长度输出之后,再输出一下模式串长度就可以了。

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<iostream>
     4 #include<algorithm>
     5 using namespace std;
     6 const int maxn=400005;
     7 const int INF=0x3f3f3f3f;
     8 char ptr[maxn];
     9 int w[maxn];
    10 void get_next(int len,int *next)
    11 {
    12     next[0]=-1;
    13     int k=-1;
    14     for(int i=1;i<=len-1;++i)
    15     {
    16         while(k>-1 && ptr[k+1]!=ptr[i])
    17             k=next[k];
    18         if(ptr[k+1]==ptr[i])
    19         {
    20             k+=1;
    21         }
    22         next[i]=k;
    23     }
    24 }
    25 int main()
    26 {
    27     int n;
    28     while(~scanf("%s",ptr))
    29     {
    30         int len=strlen(ptr);
    31         int next[len];
    32         get_next(len,next);
    33         int g=0,k=next[len-1];
    34         //w[g++]=next[len-1]+1;
    35         while(k>=0)
    36         {
    37             w[g++]=k+1;
    38             k=next[k];
    39 
    40         }
    41         sort(w,w+g);
    42         for(int i=0;i<g;++i)
    43             if(w[i]!=w[i-1] && i!=0)
    44             printf("%d ",w[i]);
    45             else if(i==0) printf("%d ",w[i]);
    46         printf("%d
    ",len);
    47     }
    48     return 0;
    49 }
    View Code

    扩展kmp:

    扩展KMP求的是对于原串S1的每一个后缀子串与模式串S2的最长公共前缀。它有一个next[]数组和一个extend[]数组。

    next[i]表示为模式串S2中以i为起点的后缀字符串和模式串S2的最长公共前缀长度.

    extend[i]表示为以字符串S1中以i为起点的后缀字符串和模式串S2的最长公共前缀长度.

    复杂度:拓展kmp算法的总体复杂度为O(n+m)的。其中n为母串的长度,m为子串的长度。

    算法过程:

    S1=aaabaaaaaab
    S2=aaaaab
    第一步,我们先对原串S1和模式串S2进行逐一匹配,直到发生不配对的情况。我们可以看到,S1[0]=S2[0],S1[1]=S2[1],S1[2]=S2[2],S1[3] ≠S2[3],此时匹配失败,第一步结束,我们得到S1[0,2]=S2[0,2],即extend[0]=3;
    Extend[0]的计算如第一步所示,那么extend[1]的计算是否也要从原串S1的1位置,模式串的0位置开始进行逐一匹配呢?扩展KMP优化的便是这个过程。从extend[0]=3的结果中,我们可以知道,S1[0,2]=S2[0,2],那么S1[1.2]=S2[1,2]。因为next[1]=4,所以S2[1,4]=S2[0,3],即S2[1,2]=S[0,1],可以得出S1[1,2]=S2[1,2]=S2[0,1],然后我们继续匹配,S1[3] ≠S2[3],匹配失败,extend[1]=2;
    之后也这样
     

    给出n组询问,每一组询问包含两个字符串t s,问s中是否包含t。(t中有’?’,’?’可以代替任何字符)。

     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 #include<string>
     5 #include<algorithm>
     6 using namespace std;
     7 int l1,l2,n;
     8 char s[100009],t[100009];
     9 int nxt[100009];
    10 void next_make()
    11 {
    12     int t1=0,t2=-1;
    13     nxt[0]=-1;
    14     while(t1<l1)
    15     {
    16         if(t2==-1||t[t1]==t[t2]||t[t2]=='?') nxt[++t1]=++t2;
    17         else t2=nxt[t2];
    18     }
    19 }
    20 void match(int t1,int t2)
    21 {
    22     while(t1<l1&&t2<l2)
    23     {
    24         if(t1==-1||t[t1]==s[t2]||t[t1]=='?') t1++,t2++;
    25         else t1=nxt[t1];
    26     }
    27     if(t1==l1) printf("God bless You!
    ");
    28     else printf("Game Over!
    ");
    29     return; 
    30 }
    31 int main()
    32 {
    33     scanf("%d",&n);
    34     while(n--)
    35     {
    36         cin>>t;cin>>s;
    37         l1=strlen(t);
    38         l2=strlen(s);
    39         next_make();
    40         match(0,0);
    41     }
    42     return 0;
    43 } 
    View Code

    Manacher算法:

    当我们遇到字符串为“aaaaaaaaa”,之前的算法就会发生各个回文相互重叠的情况,会产生重复计算,然后就产生了一个问题,能否改进?答案是能,1975年,一个叫Manacher发明了Manacher Algorithm算法,俗称马拉车算法,其时间复杂为O(n)。

    例题:

    HDU - 3613

    给你一个字符串(仅含字母),每个字母有一个价值,把字符串切断成两部分,若其中一部分为回文串,则其价值等于各字母价值和相加;否则为0。总价值为两部分价值相加,求最大价值。

    题解:

    先用manacher求出回文子串长度,再枚举切割点,若切后子串的中点是回文中心,且回文长度扩展到i点,则这个子串为回文,记录价值。最后输出最大价值。

    代码:

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<iostream>
     4 #include<queue>
     5 #include<algorithm>
     6 using namespace std;
     7 const int maxn=5e5+10;
     8 char str[maxn*2],s[maxn];
     9 int len,Len[maxn*2],val[maxn],v[30];
    10 void init()
    11 {
    12     memset(str,0,sizeof(str));
    13     int k=0;
    14     str[k++]='$';
    15     for(int i=1;i<=len;++i)
    16     {
    17         str[k++]='#';
    18         str[k++]=s[i];
    19     }
    20     str[k++]='#';
    21     len=k;
    22 }
    23 void manacher()
    24 {
    25     Len[0]=0;
    26     int sum=0;
    27     int id,mx=0;
    28     for(int i=1;i<len;++i)
    29     {
    30         if(i<mx) Len[i]=min(mx-i,Len[2*id-i]);
    31         else Len[i]=1;
    32         while(str[i-Len[i]]==str[i+Len[i]]) Len[i]++;
    33         if(Len[i]+i>mx)
    34         {
    35             mx=Len[i]+i;
    36             id=i;
    37         }
    38     }
    39 }
    40 int main()
    41 {
    42     int t;
    43     scanf("%d",&t);
    44     while(t--)
    45     {
    46         for(int i=0;i<26;++i)
    47             scanf("%d",&v[i]);
    48         scanf("%s",s+1);
    49         len=strlen(s+1);
    50         for(int i=1;i<=len;++i)
    51         {
    52             val[i]=val[i-1]+v[s[i]-'a'];
    53         }
    54         int n=len;
    55         init();
    56         manacher();
    57         int ans=0;
    58         for(int i = 1; i < n; i++){
    59             int tmp = 0;
    60             if(Len[i + 1] - 1 == i){
    61                 if(i % 2){
    62                     tmp += val[i / 2] * 2 + v[s[(i + 1) / 2] - 'a'];
    63                 }
    64                 else{
    65                     tmp += val[i / 2] * 2;
    66                 }
    67             }
    68             if(Len[i + n + 1] - 1 == n - i){
    69                 if((n - i) % 2){
    70                     tmp += (val[i + (n - i) / 2] - val[i]) * 2 + v[s[i + (n + 1- i) / 2] - 'a'];
    71                 }
    72                 else{
    73                     tmp += (val[i + (n - i) / 2] - val[i]) * 2;
    74                 }
    75             }
    76             ans = max(ans, tmp);
    77         }
    78         printf("%d
    ",ans);
    79     }
    80     return 0;
    81 }
    View Code

    板子:

      1 /*  这是一个板子 
      2 #include<iostream>
      3 #include<string>
      4 #include<cstring>
      5 #include<algorithm>
      6 #include<vector>
      7 #include<cmath>
      8 using namespace std;
      9 //算法主体
     10 int maxLcsplength(string str) {
     11     //空字符串直接返回0
     12     if (str.length() == 0) {
     13         return 0;
     14     }
     15     //记录下manacher字符串的长度,方便后面使用
     16     int len = (int)(str.length() * 2 + 1);
     17     //开辟动态数组chaArr记录manacher化的字符串
     18     //开辟动态数组pArr记录每个位置的回文半径
     19     char *chaArr = new char[len];
     20     int* pArr = new int[len];
     21     int index = 0;
     22     for (int i = 0; i < len;i++) {
     23         chaArr[i] = (i & 1) == 0 ? '#' : str[index++];
     24     }
     25     //到此完成对原字符串的manacher化
     26     //R是最右回文边界,C是R对应的最左回文中心,maxn是记录的最大回文半径
     27     int R = -1;
     28     int C = -1;
     29     int maxn = 0;
     30     //开始从左到右遍历
     31     for (int i = 0; i < len; i++) {
     32         //第一步直接取得可能的最短的回文半径,当i>R时,最短的回文半径是1,反之,最短的回文半径可能是i对应的i'的回文半径或者i到R的距离
     33         pArr[i] = R > i ? min(R - i, pArr[2 * C - i]) : 1;
     34         //取最小值后开始从边界暴力匹配,匹配失败就直接退出
     35         while (i + pArr[i]<len && i - pArr[i]>-1) {
     36             if (chaArr[i + pArr[i]] == chaArr[i - pArr[i]]) {
     37                 pArr[i]++;
     38             }
     39             else {
     40                 break;
     41             }
     42         }
     43         //观察此时R和C是否能够更新
     44         if (i + pArr[i] > R) {
     45             R = i + pArr[i];
     46             C = i;
     47         }
     48         //更新最大回文半径的值
     49         maxn = max(maxn, pArr[i]);
     50     }
     51     //记得清空动态数组哦
     52     delete[] chaArr;
     53     delete[] pArr;
     54     //这里解释一下为什么返回值是maxn-1,因为manacherstring的长度和原字符串不同,所以这里得到的最大回文半径其实是原字符串的最大回文子串长度加1,有兴趣的可以自己验证试试
     55     return maxn - 1;
     56 }
     57 int main()
     58 {
     59     string s1 = "";
     60     cout << maxLcsplength(s1) << endl;
     61     string s2 = "abbbca";
     62     cout << maxLcsplength(s2) << endl;
     63     return 0;
     64 }
     65 */
     66 
     67 
     68 /* 
     69 #include<stdio.h>
     70 #include<string.h>
     71 #include<iostream>
     72 #include<queue>
     73 #include<algorithm>
     74 using namespace std;
     75 const int maxn=5e5+10;
     76 char str[maxn*2],s[maxn];
     77 int len,Len[maxn*2],val[maxn],v[30];
     78 void init()
     79 {
     80     memset(str,0,sizeof(str));
     81     int k=0;
     82     str[k++]='$';
     83     for(int i=1;i<=len;++i)
     84     {
     85         str[k++]='#';
     86         str[k++]=s[i];
     87     }
     88     str[k++]='#';
     89     len=k;
     90 }
     91 void manacher()
     92 {
     93     Len[0]=0;
     94     int sum=0;
     95     int id,mx=0;
     96     for(int i=1;i<len;++i)
     97     {
     98         if(i<mx) Len[i]=min(mx-i,Len[2*id-i]);
     99         else Len[i]=1;
    100         while(str[i-Len[i]]==str[i+Len[i]]) Len[i]++;
    101         if(Len[i]+i>mx)
    102         {
    103             mx=Len[i]+i;
    104             id=i;
    105         }
    106     }
    107 }
    108 int main()
    109 {
    110     int t;
    111     scanf("%d",&t);
    112     while(t--)
    113     {
    114         for(int i=0;i<26;++i)
    115             scanf("%d",&v[i]);
    116         scanf("%s",s+1);
    117         len=strlen(s+1);  //3
    118         
    119         //printf("%d**
    ",len); //3
    120         //init();
    121         //manacher();
    122         //printf("%d**
    ",len);  //8,这个是字符串处理完之后的长度 
    123         //for(int i=0;i<=len;++i){
    124         //    printf("%d %d
    ",i,Len[i]);
    125         //} 
    126         
    127         
    128         for(int i=1;i<=len;++i)
    129         {
    130             val[i]=val[i-1]+v[s[i]-'a'];
    131         }
    132         int n=len;
    133         init();
    134         manacher();
    135         int ans=0;
    136         for(int i = 1; i < n; i++){
    137             int tmp = 0;
    138             if(Len[i + 1] - 1 == i){
    139                 if(i % 2){
    140                     tmp += val[i / 2] * 2 + v[s[(i + 1) / 2] - 'a'];
    141                 }
    142                 else{
    143                     tmp += val[i / 2] * 2;
    144                 }
    145             }
    146             if(Len[i + n + 1] - 1 == n - i){
    147                 if((n - i) % 2){
    148                     tmp += (val[i + (n - i) / 2] - val[i]) * 2 + v[s[i + (n + 1- i) / 2] - 'a'];
    149                 }
    150                 else{
    151                     tmp += (val[i + (n - i) / 2] - val[i]) * 2;
    152                 }
    153             }
    154             ans = max(ans, tmp);
    155         }
    156         printf("%d
    ",ans);
    157         
    158     }
    159     return 0;
    160 } 
    161 */
    162 
    163 
    164 
    165 /*
    166 
    167 #include <cstdio>
    168 
    169 #include <cmath>
    170 
    171 #include <cstring>
    172 
    173 #include <cstdlib>
    174 
    175 #include <iostream>
    176 
    177 #include <algorithm>
    178 
    179 #include <queue>
    180 
    181 #include <stack>
    182 
    183 using namespace std;
    184 
    185 char a[51000005];
    186 
    187 char s[102000005];
    188 
    189 int p[51000005];
    190 
    191 int maxp,p0,l;
    192 
    193 int ans=1;
    194 
    195 void manacher()
    196 
    197 {
    198 
    199     for(int i=1;i<l;i++)
    200 
    201     {
    202 
    203         if(i<maxp)
    204 
    205         {
    206 
    207             int j=(p0<<1)-i;
    208 
    209             p[i]=min(p[j],p[p0]+p0-i);
    210 
    211         }else
    212 
    213         {
    214 
    215             p[i]=1;
    216 
    217         }
    218 
    219         while(s[i-p[i]]==s[i+p[i]])
    220 
    221         {
    222 
    223             p[i]++;
    224 
    225         }
    226 
    227         if(i+p[i]>maxp)
    228 
    229         {
    230 
    231             p0=i;
    232 
    233             maxp=i+p[i];
    234 
    235         }
    236 
    237     }
    238 
    239 }
    240 
    241 int main()
    242 
    243 {
    244 
    245     scanf("%s",a+1);
    246 
    247     l=strlen(a+1);
    248 
    249     p[1]=1;
    250 
    251     s[0]=s[1]='#';
    252 
    253     for(int i=1;i<=l;i++)
    254 
    255     {
    256 
    257         s[2*i]=a[i];
    258 
    259         s[2*i+1]='#';
    260 
    261     }
    262 
    263     l=(l<<1)+2;
    264 
    265     s[l]='0';
    266 
    267     manacher();
    268 
    269     for(int i=1;i<=l;i++)
    270 
    271     {
    272 
    273         ans=max(ans,p[i]-1);
    274 
    275     }
    276 
    277     printf("%d
    ",ans);
    278 
    279     return 0;
    280 
    281 }
    282 
    283 */ 板子 
    View Code
  • 相关阅读:
    Java数据结构和算法——汉诺塔问题
    svn的使用
    Struts+iBatis+Spring+mysql整合开发
    Java Collection
    IOS推送功能的实现(javapns)
    [工具库]JFileDownloader工具类——多线程下载网络文件,并保存在本地
    xsl 中 foreach 的使用
    网页制作技巧24条
    js 实现 datagrid 鼠标移动时datagrid 表的该变
    How to extend ASP.NET datagrid for multiselection of data rows.
  • 原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/11626451.html
Copyright © 2020-2023  润新知