ACM模板 字符串

来源:互联网 发布:淘宝api查询商品 编辑:程序博客网 时间:2024/06/05 17:46

@(ACM模板)

      • KMP
        • MP算法
        • KMP算法
        • KMP求循环节
        • 扩展KMP
      • hash函数
          • 生成hash函数
          • 字符串匹配
      • Manacher算法求最长回文子串
      • 后缀数组
      • Trie树
      • AC自动机

1. KMP

1. MP算法

  • 此为MP算法,KMP对fail数组进行了优化
  • 对于文本串T和模式串P,判断P是否为T的子串,若是,则返回匹配位置
  • 复杂度O(n+m),其中n和m分别为T和P的长度
  • 若P非T的子串,返回-1
  • 字符数组的下标从0开始,lost的下标从0开始
  • kmp返回值下标从1开始(因为符合通常习惯)
#include<bits/stdc++.h>namespace KMP{    const int maxn = 1e6 + 5;//字符串长度    int fail[maxn];//fail指针,fail[i]代表i失配后,前面的字符串s[0...i-1]中,满足”前缀等于后缀“的前缀里,最长的前缀的位置加一    //也就是说,fail指针指向的是失配后“待匹配”的位置    void getFail(char *P)    {        int m = strlen(P);        fail[0] = fail[1] = 0;        for(int i = 1; i < m; ++ i)        //寻找位置i“前缀等于后缀”的最大长度,作为fail[i+1]        {            //先不管字符i,找前面的“前缀等于后缀”的最大长度            int j = fail[i];            //然后比较位置i            //重复这个过程直到匹配            while(j && P[i] != P[j]) j = fail[j];            //若一直匹配不到,j会逐渐减小到0            //这时候需要判断一下是否匹配到了            fail[i + 1] = (P[i] == P[j])? j + 1 : 0;        }    }    int finda(char *T, char *P)//T为文本串,P为模式串    //有解返回开始位置,无解返回-1    {        int n = strlen(T);        int m = strlen(P);        getFail(P);        int j = 0;//模式串的待匹配结点        for(int i = 0; i < n; ++ i)//文本串当前指针        {            while(j && P[j] != T[i]) j = fail[j];//顺着fail指针走,直到可以匹配or走到头            if(P[j] == T[i]) ++ j;//更新待匹配位置            if(j == m)//全部都匹配完了                return i - m + 1 + 1;//返回1-indexed的位置        }        return -1;    }}

2. KMP算法

待整理。。。。

#include<iostream>  #include<cstring>  #include<cstdio>  #include<algorithm>  using namespace std;  #define N 100010  char str1[N], str2[N];  int nextval[N];  int lens, lenp;  void getnext(const char *p, int nextval[]) //前缀函数(滑步函数)  {      int i = 0, j = -1;      nextval[0] = -1;      while(i != lenp)      {          if(j == -1 || p[i] == p[j]) //(全部不相等从新匹配 || 相等继续下次匹配)          {              //++i,++j之后,再次判断p[i]与p[j]的关系            ++i, ++j;              if(p[i] != p[j]) //abcdabce                  nextval[i] = j;                  //next[i] = next[j];                 //这里其实是优化了后的,也可以仍是next[i]=j                //当str[i]==str[j]时,如果str[i]匹配失败,那么换成str[j]肯定也匹配失败,所以不是令next[i]=j,而是next[i] = next[j],跳过了第j个字符,            //即省去了不必要的比较,优化前的next[i]表示前i个字符中前缀与后缀相同的最大长度            else //abcabca                  nextval[i] = nextval[j];          }          else              j = nextval[j]; //子串移动到第nextval[j]个字符和主串相应字符比较      }      cout<<"前缀函数为:"<<endl;      for(int i = 0; i < lenp; ++i)          printf("%d", nextval[i]);      cout<<endl;  }  int KMP(char *s, char *p, int nextval[]) //KMP算法  {      int i = 0, j = 0; //s和j字符串从头开始比较      while(i != lens && j != lenp)      {          if(s[i] == p[j]) //相等继续匹配              ++i, ++j;          else          {              if(nextval[j] == -1) //-1代表与p[0]间接比较过,需要主串后移,p重新从头匹配                  ++i, j = 0;              else                  j = nextval[j]; //直接右移nextval[j]位与s[i]比较          }      }      if(j == lenp) //返回从主串第几个元素开始匹配          return i - j;      else          return -1;  }  int main() //主串子串位置从0开始  {      int pos;      while(~scanf("%s%s", str1, str2)) //str1为主串,str2为子串      {          lens = strlen(str1);          lenp = strlen(str2);          if(lens < lenp) //主串长度<子串长度          {              printf("主串长度不应小于子串长度!\n");              continue;          }          getnext(str2, nextval); //求子串的前缀函数          pos = KMP(str1, str2, nextval);          if(pos == -1)              printf("主串中不含有子串\n");          else              printf("子串从主串的第 %d 个元素开始匹配\n", pos);      }      return 0;  }

3. KMP求循环节

  1. 注释部分为求前缀循环节
const int maxn = 1e6+5;int fail[maxn];char s[maxn];void getFail(char* P){    int m = strlen(P);    fail[0] = fail[1] = 0;    for(int i = 1; i < m; i++)    {        int j = fail[i];        while(j && P[i] != P[j]) j = fail[j];        fail[i+1] = P[i] == P[j] ? j+1 : 0;    }}int repetend(char* s){    getFail(s);    int n = strlen(s);    int len;//循环节长度    int period;//循环节周期数//下面三段代码选择一段    //01. 求该字符串的循环节    len = n - fail[n];    period = n/len;    if(n % len== 0) return len;    else return n;    //02. 求该字符的前缀的循环节//    for(int i = 2; i <= n; i++)//考察长度为i的前缀    {        len = i - fail[i];//循环节长度        period = i/len;//循环节周期数        if(i != len && i % len == 0)            printf("%d %d %d\n", i, len, period);    }    //03. 求该字符最少在结尾补上几个字符,使其成为周期循环字符串,且周期数大于1    len = n - fail[n];    if(len != n && n % len== 0) return 0;    else        return len - fail[n] % len; //取余的作用:abcab,去掉abc}

3. 扩展KMP

#include<bits/stdc++.h>using namespace std;typedef long long LL;const int maxn = 1e6 + 5;char s1[maxn], s2[maxn];struct ExtendKMP{    //模式串P(Pattern)长度m,文本串T(Text)长度n    //nxt[i]:T[i..n-1]与T的LCP长度    //extend[i]:P[i..m-1]与T的LCP长度    int nxt[maxn], extend[maxn];    void getNext(char *P)    {        int m = strlen(P);        nxt[0] = m;        int i = 0;        while(P[i] == P[i+1]) ++i;        nxt[1] = i;        int id = 1;        for(i = 2; i < m; ++ i)        {            if(nxt[i-id] + i < id + nxt[id]) nxt[i] = nxt[i-id];            else            {                int j = nxt[id] + id - i;                if(j < 0) j = 0;                while(i+j < m && P[j] == P[j+i]) ++j;                nxt[i] = j;                id = i;            }        }    }    void getExtend(char *P, char *T)    {        int m = strlen(P);        int n = strlen(T);        getNext(T);        int i = 0;        while(i < m && i < n && P[i] == T[i]) ++i;        extend[0] = i;        int id = 0;        for(int i = 1; i < m; ++i)        {            if(nxt[i-id]+i < extend[id]+id) extend[i] = nxt[i-id];            else            {                int j = extend[id] + id - i;                if(j < 0) j = 0;                while(i + j < m && j < n && P[j+i] == T[j]) ++j;                extend[i] = j;                id = i;            }        }    }};

2. hash函数

1. 生成hash函数

下面计算了字符数组的hash值,要求s[l]…s[r]这个字串的hash,用getHash(l,r)即可
h1=s1
h2=s1b1+s2
h3=s1b2+s2b1+s3

hr=s1br1+s2br2++sl1br1+1++sr
hl1=s1bl2+s2bl3++sl1
hrhl1br1+1=slbrl+s2brl1++sr

注意:
- 字符数组下标从1开始

typedef unsigned long long ull;const int maxn = 1e5+7;const ull base = 163;char s[maxn];ull hah[maxn];ull pw[maxn];void calcHash(char* s){    pw[0]  = 1;    hah[0] = 0;    int n = strlen(s+1);    for(int i = 1; i < maxn; i++) pw[i] = pw[i-1] * base;    for(int i = 1; i <= n; i++) hah[i] = hah[i-1] *  base + s[i];}ull getHash(int l, int r){    return hah[r] - hah[l-1] * pw[r-l+1];}int main(){    scanf("%s", s+1);    calcHash(s);    return 0;}
2. 字符串匹配

1中的代码加上下面的函数

int strMatch(char* T, char* P){    int n = strlen(T+1), m = strlen(P+1);    initHash(T, hah);    initHash(P, hah2);    int h = getHash(1, m, hah2);    for(int i = 1; i + m - 1 <= n; i++)        if(getHash(i, i+m-1, hah) == h) return i;    return -1;}int main(){    scanf("%s%s", T+1, P+1);//start with 1!!!    return 0;}

4. Manacher算法(求最长回文子串)

const int maxn = 1e3+5;int p[maxn];string Manacher(string s){    string t = "@#";    for (int i = 0; i < s.size(); ++i)    {        t += s[i];        t += "#";    }    memset(p, 0, sizeof p);    int mx = 0, id = 0, resLen = 0, resCenter;    for(int i = 1; i < t.size(); ++i)    {        p[i] = mx>i ? min(p[2*id-i], mx-i) : 1;        while(t[i+p[i]] == t[i-p[i]]) ++p[i];        if(mx < i+p[i])        {            mx = i + p[i];            id = i;        }        if(resLen < p[i])        {            resLen = p[i];            resCenter = i;        }    }    return s.substr((resCenter - resLen) / 2, resLen-1 );}

5. 后缀数组

#include<bits/stdc++.h>using namespace std;typedef long long LL;const int maxn = 1e5+7;char s[maxn];int sa[maxn], t[maxn], t2[maxn], c[maxn], n;void build_sa(int m){  b     int *x = t, *y = t2;    //index sort    for(int i = 0; i < m; ++i) c[i] = 0;    for(int i = 0; i < n; ++i) ++c[x[i] = s[i]];    for(int i = 1; i < m; ++i) c[i] += c[i-1];    for(int k = 1; k <= n; k <<= 1)    {        int p = 0;        //直接利用sa数组排序第二关键字        for(int i = n - k; i < n; ++i)            if(sa[i] >= k) y[p++] = sa[i] - k;        //基数排序第一关键字        for(int i = 0; i < m; ++i) c[i] = 0;        for(int i = 0; i < n; ++i) ++c[x[y[i]]];        for(int i = 0; i < m; ++i) c[i] += c[i-1];        for(int i = n - 1; i >= 0; --i) sa[--c[x[y[i]]]] = y[i];        //根据sa和y数组计算新的x数组        swap(x, y);        p = 1;        x[sa[0]] = 0;        for(int i = 1; i < n; ++i)            x[sa[i]] = (y[sa[i-1]] == y[sa[i]] && y[sa[i-1]+k] == y[sa[i]+k]) ? (p-1) : p++;        if(p >= n) break;        m = p;    }}int m;//模板长度。简单起见存为全局变量int cmp_suf(char *pattern, int p)//判断模板s是否为后缀p的前缀{    return strncmp(pattern, s + sa[p], m);}int finda(char *P){    m = strlen(P);    if(cmp_suf(P, 0) < 0 || cmp_suf(P, n-1) > 0) return -1;    int l = 0, r = n-1;    while(l <= r)    {        int mid = (l + r) >> 1;        int res = cmp_suf(P, mid);        if(!res) return mid;        if(res < 0)            r = mid - 1;        else            l = mid + 1;    }    return -1;}int main(){    return 0;}

6. Trie树

注意,根据Trie节点中内容的不同(如下面代码中为小写字母),需要注意maxm的不同、字符到id的映射关系不同

const int maxn = 1e5+7;//number of lettersconst int maxm = 26;//size of lower case letters//a Trie of lower case stringsstruct Trie{    int ch[maxn][maxm];    int val[maxn];//assume that val is positive    int tot;//节点总数    Trie()    {        tot = 1;        memset(ch[0], 0, sizeof ch[0]);    }    //insert an string s, whose value is v; note that v != 0. 0 stands for "not an end point"    void add(char *s, int v)    {        int u = 0;//root        int n = strlen(s);        for(int i = 0; i < n; ++i)        {            int id = s[i] - 'a';            if(!ch[u][id])//the point does not exist            {                memset(ch[tot], 0, sizeof ch[tot]);                val[tot] = 0;//the val of middle point is 0                ch[u][id] = tot++;            }            u = ch[u][id];        }        val[u] = v;    }    int finda(char *s)//return -1 if not exists    {        int u = 0;//root;        int n = strlen(s);        for(int i = 0; i < n; ++i)        {            int id = s[i] - 'a';            if(!ch[u][id]) return 0;            u = ch[u][id];        }        return val[u];    }};

7. AC自动机

#include<bits/stdc++.h>using namespace std;typedef long long LL;const int maxn = 1e6 + 5;const int maxm = 26;struct ACautomaton{    int ch[maxn][maxm];//ch[i][c]代表结点i的c孩子;初始有一个根节点,代表空字符串    int val[maxn];//val为正代表这是一个模式串单词结点    int fail[maxn];//suffix link,代表当前路径字符串的最大前缀    int last[maxn];//output link, 上一个单词结点    int tot;//Trie树中结点总数    void init()    {        tot = 1;        val[0] = 0;        memset(ch[0], 0, sizeof ch[0]);    }    //O(n),n为所有模式总长度    void add(char *P, int v)//插入模式串,值为v    {        int u = 0;//当前结点        int n = strlen(P);        for(int i = 0; i < n; ++i)        {            int c = P[i] - 'a';            if(!ch[u][c])//若当前结点无c孩子,则创造一个            {                memset(ch[tot], 0, sizeof ch[tot]);                val[tot] = 0;//中间结点的值为零                ch[u][c] = tot++;            }            u = ch[u][c];//走向当前结点的c孩子        }        //现在走到了模式串的结尾结点        val[u] += v;    }    //O(tot)的    void getFail()//构造fail指针和last指针    //使用BFS,因为fail指针一定指向长度更短的字符串    {        queue<int> q;        fail[0] = 0;        //初始化队列        for(int c = 0; c < maxm; ++c)        {            int u = ch[0][c];            if(u)            {                fail[u] = last[u] = 0;//第一层结点的fail都是根节点                q.push(u);//将第一层结点加入队列            }        }        //BFS        while(!q.empty())        {            int cur = q.front();            q.pop();            for(int c = 0; c < maxm; ++c)//为cur结点的c孩子添加fail指针            {                int u = ch[u][c];                if(!u)//当前结点没有c孩子                {                    ch[cur][c] = ch[fail[cur]][c];//沿fail往上找,因为fail指针指向的还是这个后缀                    continue;                }                q.push(u);//c孩子入队                int v = fail[cur];                while(v && !ch[v][c]) v = fail[v];//若后缀结点无c孩子,就沿fail指针一直网上找                fail[u] = ch[v][c];//给c孩子添加fail指针                //若c孩子的fail指针指向模式串结点,则c孩子的last指向fail指针位置即可,因为这就是最长的                //否则指向fail指针指向的结点的last即可                if(val[fail[u]]) last[u] = fail[u];                else last[u] = last[fail[u]];            }        }    }};
原创粉丝点击