「TJOI2019」甲苯先生和大中锋的字符串
这个天(四声)津(轻声)OI 怎么出板题啊= =
简述
\(T\) 组数据,每次给定一字符串 \(s\) 与参数 \(k\)。
对于 \(s\) 中出现次数为 \(k\) 的子串,求它们的长度中出现次数最多的长度数(如果有多个输出最长长度)。
\(1\le T\le 100\),\(1\le |s|\le 10^5\),\(\sum |s|\le 3\times 10^6\)。
1S,128MB。
分析
后缀树
这是我一开始考虑的方向= =
先建出 \(s\) 的后缀树,根据后缀的前缀是子串的性质,一条边上代表的所有子串的出现次数即为其子树中终止状态的个数,dfs 一遍即可求得。
一条边上的子串长度连续,问题可以抽象成数轴上被线段覆盖次数最多的点的位置,差分即可。
复杂度 \(O(\sum |s|)\) 级别。
SAM
因为我不会 Ukk,建后缀树还要写个 SAM,索性直接在 SAM 上做了= =
根据 \(\operatorname{endpos}\) 的性质,一个状态代表的所有子串的出现次数即为 \(\operatorname{endpos}\) 的大小,可以在 parent 树上 DP 维护子树内终止状态个数求得。
之后枚举每个 \(\operatorname{endpos}\) 的大小等于 \(k\) 的状态,状态表示的子串长度连续,问题与上面后缀树类似,差分即可。
复杂度 \(O(\sum |s|)\) 级别。
代码
以下是 SAM。
//知识点:SAM,差分
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, k;
char s[kN];
//=============================================================
inline int read() {
int f = 1, w = 0;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) {
w = (w << 3) + (w << 1) + (ch ^ '0');
}
return f * w;
}
void Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
namespace SAM {
int node_num, last;
int tr[kN][26], len[kN], link[kN];
int cnt[kN], id[kN], size[kN];
int diff[kN];
void Init() {
node_num = last = 1;
memset(cnt, 0, sizeof (cnt));
memset(size, 0, sizeof (cnt));
memset(tr, 0, sizeof (tr));
memset(diff, 0, sizeof (diff));
}
void Insert(int ch_) {
int p = last, now = last = ++ node_num;
size[now] = 1;
len[now] = len[p] + 1;
for (; p && ! tr[p][ch_]; p = link[p]) tr[p][ch_] = now;
if (! p) {
link[now] = 1;
return ;
}
int q = tr[p][ch_];
if (len[q] == len[p] + 1) {
link[now] = q;
return ;
}
int newq = ++ node_num;
len[newq] = len[p] + 1;
memcpy(tr[newq], tr[q], sizeof (tr[q]));
link[newq] = link[q];
link[q] = link[now] = newq;
for (; p && tr[p][ch_] == q; p = link[p]) tr[p][ch_] = newq;
}
void Build() {
for (int i = 1; i <= node_num; ++ i) cnt[len[i]] ++;
for (int i = 1; i <= node_num; ++ i) cnt[i] += cnt[i - 1];
for (int i = 1; i <= node_num; ++ i) id[cnt[len[i]] --] = i;
for (int i = node_num; i; -- i) size[link[id[i]]] += size[id[i]];
}
void Solve() {
int maxcnt = 1, ans = -1;
for (int i = 1; i <= node_num; ++ i) {
if (size[i] == k) {
diff[len[link[i]] + 1] ++;
diff[len[i] + 1] --;
}
}
for (int i = 1; i <= n; ++ i) {
diff[i] += diff[i - 1];
if (diff[i] >= maxcnt) {
maxcnt = diff[i];
ans = i;
}
}
printf("%d\n", ans);
}
}
//=============================================================
int main() {
int T = read();
while (T --) {
SAM::Init();
scanf("%s", s + 1);
n = strlen(s + 1), k = read();
for (int i = 1; i <= n; ++ i) SAM::Insert(s[i] - 'a');
SAM::Build(); SAM::Solve();
}
return 0;
}
作者@Luckyblock,转载请声明出处。