1. 程式人生 > >KMP算法題記

KMP算法題記

中一 ref org urn 最小 next數組的理解 之一 nac problem

照著這篇博客刷一下。 自己也做一下筆記

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

KMP算法題記