您的位置:首页 > 其它

KMP、扩展KMP、Manacher习题

2020-04-01 18:40 141 查看

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

对于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
) % 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

  • 点赞
  • 收藏
  • 分享
  • 文章举报
dlsq3814 发布了0 篇原创文章 · 获赞 0 · 访问量 225 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: