• KMP、扩展KMP、Manacher习题


    照着这篇博客刷一下。 自己也做一下笔记

    对于KMP算法,可以看我之前总结的这篇博客

    hdu 3613 Best Reward

    给一个字符串,字符由a~z构成,每个字符有一个权值。在某一点将字符串切成2半,若切成的字符串是回文的,则值为字符值之和,否则为0,问最大价值是多少?

    设原串为S,S的逆记作T。则以T为主串,S为模式串做EKMP,若extend1[m-i]+m-i==m,则在i点切割,S[0~i)是回文的。若以S为主串,T为模式串做EKMP,若extend2[i]+i==m,则S[i,m)是回文的。具体为什么,可以看这个推理

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define maxN 500100
    int v[30], sum[maxN], nxt[maxN], extend1[maxN], extend2[maxN], cas;
    char a[maxN], b[maxN];
    
    void PRE_EKMP(char *x, int m, int *nxt) {
      nxt[0] = m;
      int j = 0;
      while (j + 1 < m && x[j] == x[j + 1]) ++j;
      nxt[1] = j;
      int k = 1;
      FOR(i, 2, m - 1) {
        int p = nxt[k] + k - 1;
        int L = nxt[i - k];
        if (i + L - 1 < p) nxt[i] = L;
        else {
          j = max(0, p - i + 1);
          while (i + j < m && x[i + j] == x[j]) ++j;
          nxt[i] = j;
          k = i;
        }
      }
    }
    void EKMP(char *x, int m, char *y, int n, int *nxt, int *extend) {
      PRE_EKMP(x, m, nxt);
      int j = 0;
      while (j < n && j < m && x[j] == y[j]) ++j;
      extend[0] = j;
      int k = 0;
      FOR(i, 1, n - 1) {
        int p = extend[k] + k - 1;
        int L = nxt[i - k];
        if (i + L < p + 1) extend[i] = L;
        else {
          j = max(0, p - i + 1);
          while (i + j < n && j < m && y[i + j] == x[j]) ++j;
          extend[i] = j;
          k = i;
        }
      }
    }
    
    int main () {
      // freopen("data.in", "r", stdin);
      scanf("%d", &cas);
      while (cas--) {
        sum[0] = 0;
        FOR(i, 0, 26 - 1) scanf("%d", &v[i]);
        scanf("%s", a);
        int m = (int)strlen(a);
        sum[0] = v[a[0] - 'a'];
        FOR(i, 1, m - 1) sum[i] = sum[i - 1] + v[a[i] - 'a'];
        FOR(i, 0, m - 1) b[i] = a[m - i - 1];
        b[m] = 0;
    
       // EKMP(a,b)意味着a为模式串,去匹配b EKMP(a, m, b, m, nxt, extend1);
    // 前缀 EKMP(b, m, a, m, nxt, extend2); // 后缀 int ans = -1e8; FOR(i, 1, m - 1) { int sc = 0, j = m - i; if (extend1[j] + j == m) sc += sum[i - 1]; if (extend2[i] + i == m) sc += sum[m - 1] - sum[i - 1]; ans = max(ans, sc); } printf("%d ", ans); } return 0; }

    poj 3461 Oulipo 

    基于两个串a和b,问a在b中重复了几次。要对KMP进行一些修改,其实只是在模式串匹配完之后,ans++,并且让模式串的j回到原来的位置重来而已。

    #include <cstdio>
    #include <cstring>
    using namespace std;
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define maxN 1000005
    int nxt[maxN], n, m, cas, ans;
    char a[maxN], b[maxN];
    void getNxt(char *b, int m, int *nxt) {
      nxt[0] = -1;
      int i = 0, j = -1;
      while (i < m) {
        if (j == -1 || b[i] == b[j]) ++i, ++j, nxt[i] = j;
        else j = nxt[j];
      }
    }
    void kmp(char *a, int n, char *b, int m) {
      getNxt(b, m, nxt);
      int i = 0, j = 0;
      while (i < n) {
        while (-1 != j && a[i] != b[j]) j = nxt[j];
        ++i, ++j;
        if (j >= m) {
          ++ans;
          j = nxt[j];
        }
      }
    }
    int main () {
      // freopen("data.in", "r", stdin);
      scanf("%d", &cas);
      while (cas--) {
        scanf("%s%s", b, a);
        n = (int)strlen(a), m = (int)strlen(b);
        ans = 0;
        kmp(a, n, b, m);
        printf("%d
    ", ans);
      }
      return 0;
    }

    poj 2752 Seek the Name, Seek the Fame

    给你一个字符串,问前缀和后缀相同的字符串长度可以为多少?

    考的是对next数组的理解。假设串为s,长度为L,那么next[L],即是s的最长前后缀长度,是答案之一,这里设这里的最长前缀为A,最长后缀为B。更短的”前缀-后缀串“必然也是A的前缀和B的后缀的公共部分,又因为A=B,那么问题变成了A的最长公共前后缀问题,如此便可不断回溯回去。

    #include <cstdio>
    #include <cstring>
    using namespace std;
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define maxN 400005
    
    char a[maxN];
    int m, ans[maxN], nxt[maxN];
    void getNxt(char *b, int m, int *nxt) {
      nxt[0] = -1;
      int i = 0, j = -1;
      while (i < m) {
        if (j == -1 || b[i] == b[j]) ++i, ++j, nxt[i] = j;
        else j = nxt[j];
      }
    }
    int main () {
      // freopen("data.in", "r", stdin);
      while (~scanf("%s", a)) {
        m = (int)strlen(a);
        getNxt(a, m, nxt);
        int cnt = 0;
        int cur = m, j = nxt[cur];
        while (j) ans[++cnt] = j, j = nxt[j];
        FOR(i, 1, cnt) printf("%d ", ans[cnt - i + 1]);
        printf("%d
    ", m);
      }
      return 0;
    }

    poj 2406 Power Strings

    问的是一个字符串,其最多能由多少个循环节构成。可以参考这篇说明

    #include <cstdio>
    #include <cstring>
    using namespace std;
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define maxN 1000005
    char s[maxN];
    int nxt[maxN];
    void getNxt(char *b, int m, int *nxt) {
      nxt[0] = -1;
      int i = 0, j = -1;
      while (i < m) {
        if (j == -1 || b[i] == b[j]) nxt[++i] = ++j;
        else j = nxt[j];
      }
    }
    int main () {
      // freopen("data.in", "r", stdin);
      while (~scanf("%s", s) && strcmp(s, ".")) {
        int m = (int)strlen(s);
        getNxt(s, m, nxt);
        if (m % (m - nxt[m]) == 0)
          printf("%d
    ", m / (m - nxt[m]));
        else
          puts("1");
      }
      return 0;
    }

    hdu 3746 Cyclic Nacklace

    问的是最少加入几个字符能使得这个串是循环的。

    分几种情况:1,整个串无法被循环, 即nxt[m]=0,此时直接再来一个串接后面才行。

    2,本身已经是循环串,此时m%(m-nxt[m])==0,直接输出0即可。

    3,前缀是循环串,这个时候找到那个nxt[i]==0 (意味着前面就一个串,不循环),或者是i%(i-nxt[i])==0,此时即[0,i)这个串是循环串,得出最小循环节长度,设为L,答案就是L-后缀长度。

    #include <cstdio>
    #include <cstring>
    using namespace std;
    #define maxN 100006
    int nxt[maxN], cas, idx;
    char a[maxN];
    void getNxt(char *v, int m) {
      nxt[0] = -1;
      int i = 0, j = -1;
      while (i < m) {
        if (j == -1 || v[i] == v[j]) nxt[++i] = ++j;
        else j = nxt[j];
    
        if (nxt[i] == 0 || i % (i - nxt[i]) == 0)
          idx = i;
      }
    }
    
    int main () {
      // freopen("data.in", "r", stdin);
      scanf("%d", &cas);
      while (cas--) {
        scanf("%s", a);
        int m = (int)strlen(a);
        getNxt(a, m);
        
        if (nxt[m] == 0) {
          printf("%d
    ", m);
        } else if (m % (m - nxt[m]) == 0) {
          puts("0");
        } else {
          // 循环节长度
          int L = idx - nxt[idx];
          int tail = m - idx;
          printf("%d
    ", L - tail);
        }
      }
      return 0;
    }

    hdu 3336 Count the string

    问的是所有的前缀,在字符串中一共出现了几次?

    假设某个前缀A和后缀B一样,那么B相当于给A贡献了B.length()分数。于是乎问题变成了:问有多少和前缀串相同的后缀串。但是因为如果单反前后缀一样就加分,会重复计算,比如说:

    aaauvwaaa,第7和第8个a组成的aa会贡献2分,当加入第9个a成为aaa时,如果你又认为贡献3分,就会重复计算了第7个第8个连成的"aa"的分数。于是:当nxt[i]+1!=nxt[i+1]时,表示到i的后缀和到i+1的后缀是不一样的,才进行加分。

    #include <cstdio>
    using namespace std;
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define maxN 200005
    int nxt[maxN], cas, n;
    char a[maxN];
    void getNxt(char *v, int m) {
      nxt[0] = -1;
      int i = 0, j = -1;
      while (i < m) {
        if (j == -1 || v[i] == v[j]) nxt[++i] = ++j;
        else j = nxt[j];
      }
    }
    int main () {
      // freopen("data.in", "r", stdin);
      scanf("%d", &cas);
      while (cas--) {
        scanf("%d %s", &n, a);
        getNxt(a, n);
        int ans = (n + nxt[n]) % 10007;
        FOR(i, 0, n - 1) {
          if (nxt[i] && nxt[i] + 1 != nxt[i + 1])
            ans = (ans + nxt[i]) % 10007;
        }
        printf("%d
    ", ans);
      }
      return 0;
    }

    HDU 3374 String Problem  KMP+最大最小表示法:

    问的是一个字符串,其最小和最大表示的起始下标,以及各自出现的次数。

    至于次数,不论是最大还是最小,当然都是一样的,求一个循环次数即可,用KMP的next数组。

    而最小表示法的思想是怎么样的呢?:

    令i=0,j=1,k=0,表示从i开始k长度和j开始k长度的字符串相同。

    那么若s[i]=s[j],此时k++,若s[i]>s[j],意味着i位置的字典序比j位置的字典序更大,需要移动,又因为i开始和j开始有k个字符相同,所以j可以待定保留,i需要变化,变化到未知的地方,即i+=k+1即可。

    相反,若s[i]<s[j],同理j+=k+1。最后i和j中的较小者,是第一次出现最小表示的下标。

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define maxN 1000005
    int nxt[maxN];
    char a[maxN];
    void getNxt(char *v, int m) {
      nxt[0] = -1;
      int i = 0, j = -1;
      while (i < m) {
        if (j < 0 || v[i] == v[j]) nxt[++i] = ++j;
        else j = nxt[j];
      }
    }
    // mode=0:min else:max
    int minMaxRep(char *s, int L, int mode) {
      int i = 0, j = 1, k = 0, t;
      while (i < L && j < L && k < L) {
        t = s[(i + k) % L] - s[(j + k) % L];
        if (t == 0) ++k;
        else {
          if (mode == 0) {
            if (t > 0) i += k + 1;
            else j += k + 1;
          } else {
            if (t > 0) j += k + 1;
            else i += k + 1;
          }
          if (i == j) ++j;
          k = 0;
        }
      }
      return min(i, j);
    }
    int main() {
      // freopen("data.in", "r", stdin);
      while (~scanf("%s", a)) {
        int L = (int)strlen(a);
        int p1 = minMaxRep(a, L, 0) + 1;
        int p2 = minMaxRep(a, L, 1) + 1;
        getNxt(a, L);
        if (L % (L - nxt[L]) != 0) {
          printf("%d 1 %d 1
    ", p1, p2);
        } else {
          int t = L / (L - nxt[L]);
          printf("%d %d %d %d
    ", p1, t, p2, t);
        }
      }
      return 0;
    }

    FZU 1901 Period II

    For each prefix with length P of a given string S,ifS[i]=S[i+P] for i in [0..SIZE(S)-p-1],then the prefix is a “period” of S. We want to all the periodic prefixs.

    需要输出满足period的p。 其实就是求所有的前后缀公共串。那么while(nxt[i]) 获取nxt[i],然后i=nxt[i];即可。

    再而,那p和nxt[i]的关系又是什么呢?比如abcdxxxxabcd。对于最大的nxt[L]=4而言,即公共前缀为4的时候,i=L=12,第1个a要多少才到第2个a?距离刚好是L-nxt[L]

    于是对于每个nxt[i],p就是L-nxt[i]。由于FZU挂了所以没交过,代码如下:

    #include <cstdio>
    #include <cstring>
    using namespace std;
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define maxN 1000006
    int nxt[maxN], L, cas, ans[maxN], cnt;
    char a[maxN];
    void getNxt(char *v, int m) {
      nxt[0] = -1;
      int i = 0, j = -1;
      while (i < m) {
        if (j < 0 || v[i] == v[j]) nxt[++i] = ++j;
        else j = nxt[j];
      }
    }
    
    int main () {
      // freopen("data.in", "r", stdin);
      scanf("%d", &cas);
      FOR(ca, 1, cas) {
        cnt = 0;
        scanf("%s", a);
        L = (int)strlen(a);
        getNxt(a, L);
        int cur = L;
        while (nxt[cur])
          ans[cnt++] = L - nxt[cur], cur = nxt[cur];
        ans[cnt++] = L;
        printf("Case #%d: %d
    ", ca, cnt);
        FOR(i, 0, cnt - 1) printf("%d ", ans[i]);
        puts("");
      }
      return 0;
    }

    HDU 3613 待叙述, 比较值得品味

    #include <cstdio>
    #include <cstring>
    #include <vector>
    using namespace std;
    
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define ll long long
    #define maxN 2000005
    
    // cnt代表前缀
    struct node {int sum, son[26], cnt;} nd[maxN];
    struct str{
      int st, ed;
      str() {}
      str(int s, int e) : st(s), ed(e) {}
    };
    vector<str> v;
    
    int fg[maxN][2], nxt[maxN], tot, n, pre, l;
    char s[maxN], t[maxN];
    
    void getNxt(char *a, int m) {
      nxt[0] = -1;
      int i = 0, j = -1;
      while (i < m) {
        if (j < 0 || a[i] == a[j]) nxt[++i] = ++j;
        else j = nxt[j];
      }
    }
    
    // f 0前缀 1后缀 a是模式串
    void KMP(int f, char *a, char *b, int m, int start) {
      int i = 0, j = 0;
      while (i < m) {
        if (j < 0 || a[j] == b[i]) ++i, ++j;
        else j = nxt[j];
      }
      int pre = j;
      if (f == 0) {
        while (pre) {
          fg[start + pre - 1][0] = 1;
          pre = nxt[pre];
        }
      } else {
        while (pre) {
          fg[start + l - pre][1] = 1;
          pre = nxt[pre];
        }
      }
    }
    
    void insert(int pre, char *a, int start, int L) {
      FOR(i, 0, l - 1) {
        int cur = a[i] - 'a';
        if (!nd[pre].son[cur]) {
          ++tot;
          nd[pre].son[cur] = tot;
          pre = tot;
        } else pre = nd[pre].son[cur];
        if (i + 1 < L) nd[pre].cnt += fg[start + i + 1][1];
      }
      nd[pre].sum++;
    }
    
    ll query(int start, int en, int pre) {
      ll sym = 1, ans = 0, l = en - start;
      FOR(i, start, en - 1) {
        int cur = t[i] - 'a';
        if (nd[pre].son[cur]) {
          pre = nd[pre].son[cur];
          if (fg[start + l - (i - start + 1) - 1][0] || i == en - 1)
            ans += nd[pre].sum;
        } else {
          sym = 0;
          break;
        }
      }
      if (sym) ans += nd[pre].cnt;
      return ans;
    }
    
    int main() {
      // freopen("data.in", "r", stdin);
      pre = 0;
      scanf("%d", &n);
      ll ans = 0;
      while (n--) {
        scanf("%d %s", &l, s + pre);
        v.push_back(str(pre, pre + l));
        FOR(i, pre, pre + l - 1) t[i] = s[pre + l - (i - pre + 1)];
        getNxt(s + pre, l);
        KMP(0, s + pre, t + pre, l, pre);
        getNxt(t + pre, l);
        KMP(1, t + pre, s + pre, l, pre);
        insert(0, s + pre, pre, l);
        pre += l;
      }
      FOR(i, 0, (int)v.size() - 1)
        ans += query(v[i].st, v[i].ed, 0);
      printf("%lld
    ", ans);
    }

    hdu 4513 吉哥系列故事--完美队形II

    问最长回文串,但要求左到中是非递减的,对称的,就是中到右是非递增的。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define maxN 1000005
    int cas, n, T[maxN*2];
    int s1[maxN], s2[maxN*2];
    
    void manacher() {
      s2[0] = -2, s2[1] = -1;
      int j = 2;
      FOR(i, 0, n - 1)
        s2[j++] = s1[i], s2[j++] = -1;
    
      int id = 0, mx = 0;
      T[0] = 0;
      FOR(i, 1, 2 * n + 1) {
        T[i] = (i < mx) ? min(T[2 * id - i], mx - i) : 1;
        while (s2[i - T[i]] == s2[i + T[i]]) {
          if (s2[i + T[i]] != -1) {
            if (s2[i + T[i]] <= s2[i + T[i] - 2]) T[i]++;
            else break;
          }
          T[i]++;
        }
        if (i + T[i] > mx)
          id = i, mx = i + T[i];
      }
    }
    
    int main () {
      // freopen("data.in", "r", stdin);
      scanf("%d", &cas);
      while (cas--) {
        scanf("%d", &n);
        FOR(i, 0, n - 1)
          scanf("%d", &s1[i]);
        manacher();
        int ans = 0;
        FOR(i, 1, 2 * n + 1) ans = max(ans, T[i]);
        printf("%d
    ", ans - 1);
      }
      return 0;
    }

    51nod 1554 欧姆诺姆和项链

    给数字n和k,n和k都∈[1,100000],再给一个字符串a,问前几个字符可构成这样的形式:ABAB...ABA,其中A有k+1个,B有k个,A和B可为空。前i个可以的时候输出1,否则0.

    只有两种可能:SSSS..SS,或者是SSSS..ST。

    假设为前者,那么循环节长度为i/(i-nxt[i]),设为x,又因为有k对AB,所以AB包含了x/k对S,剩下x%k个S就是A包含的S的个数,因B可以为空,所以x/k>=x%k即可。

    假设为后者,其中T是S的一个前缀,即A,与前者一样,循环节长度为i/(i-nxt[i]),设为x,又因有k对AB,那么AB包含了x/k对S,剩下x%k个S ,以及单独的T,所以只要x/k<x%k即可。

    #include <stdio.h>
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define maxN 1000009
    int f[maxN];
    char a[maxN];
    
    void getf(char *x, int m) {
      f[0] = -1;
      int i = 0, j = -1;
      while (i < m) {
        if (j < 0 || x[i] == x[j]) f[++i] = ++j;
        else j = f[j];
      }
    }
    
    int main () {
      // freopen("data.in", "r", stdin);
      int n, k;
      scanf("%d%d%s", &n, &k, a);
      getf(a, n);
      FOR(i, 1, n) {
        int x = i / (i - f[i]);
        if (i % (i - f[i])) {
          if (x / k > x % k) printf("1");
          else printf("0");
        } else {
          if (x / k >= x % k) printf("1");
          else printf("0");
        }
      }
      return 0;
    }

    51nod 1277

    给一个字符串,前缀有2个属性,长度和在字符串中出现的次数。

    问:所有前缀中,长度*字符串出现的次数 最大值是多少?

    g[i]记录长度为i的前缀出现的次数。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    #define FOR(i,a,b) for(int i=(a);i<=(b);++i)
    #define ll long long
    #define maxN 100005
    char a[maxN];
    int f[maxN], g[maxN];
    
    void init(int m) {
      f[0] = -1;
      int i = 0, j = -1;
      while (i < m) {
        if (j < 0 || a[i] == a[j]) f[++i] = ++j;
        else j = f[j];
      }
    }
    
    int main () {
      // freopen("data.in", "r", stdin);
      scanf("%s", a);
      int m = (int)strlen(a);
      init(m);
      for (int i = m; i >= 1; --i)
        g[i]++, g[f[i]] += g[i];
      ll ans = 0;
      for (ll i = 1; i <= m; ++i)
        ans = max(i * g[i], ans);
      printf("%lld
    ", ans);
      return 0;
    }
  • 相关阅读:
    mysql查找有某列但没有此列索引的表
    mysql找到所有索引
    mysq在某一刻同时获取主从库的位置点
    新书《深入应用C++11:代码优化与工程级应用》出版,感谢支持
    c++11实现一个简单的lexical_cast
    应该用bind+function取代虚函数吗?
    《深入应用C++11:代码优化与工程级应用》开始发售
    一个更好的C++序列化/反序列化库Kapok
    C++技术沙龙主要内容
    C++11模版元编程
  • 原文地址:https://www.cnblogs.com/Rosebud/p/9850122.html
Copyright © 2020-2023  润新知