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\n", 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\n", 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\n", 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\n", 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\n", m);
    } else if (m % (m - nxt[m]) == 0) {
      puts("0");
    } else {
      // 循环节长度
      int L = idx - nxt[idx];
      int tail = m - idx;
      printf("%d\n", 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\n", 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\n", p1, p2);
    } else {
      int t = L / (L - nxt[L]);
      printf("%d %d %d %d\n", 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\n", 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\n", 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\n", 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\n", ans);
  return 0;
}

 

转载于:https://www.cnblogs.com/Rosebud/p/9850122.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值