「TJOI2019」甲苯先生和大中锋的字符串

知识点:SAM,差分
原题面:LojLuogu

这个天(四声)津(轻声)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;
}
posted @ 2021-01-09 21:01  Luckyblock  阅读(82)  评论(1编辑  收藏  举报