对于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; }
基于两个串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; }
问的是一个字符串,其最多能由多少个循环节构成。可以参考这篇说明
#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; }
问的是最少加入几个字符能使得这个串是循环的。
分几种情况: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; }