CF666E Forensic Examination [后缀自动机,线段树合并]

题意:

给出一个串 \(S\),再给出 \(n\) 个串 \(T_i\), \(q\) 次询问 \(S[pl,pr]\) 在 $ T_{[l,r]}$哪个串出现次数最多。

solution:

不难想到我们找 \(S[pl,pr]\) 是可以记录 \(ed_{pr}\) 然后倍增上去找到这个区间所对应的 SAM 节点。

我们把 \(T_i\) 插入 SAM 里,并且对应节点搞上 \(i\),然后合并就好了qwq。

SAM 某个子树部分都是包含他自己的串,所以线段树合并一下就变成了子树数颜色以及找到最多颜色的问题了。

code:

// powered by c++11
// by Isaunoya
#include <bits/stdc++.h>
using namespace std;
#define pb emplace_back
int n;
const int maxn = 1e6 + 61;
char s[maxn], buf[maxn];

struct PII {
  int x, y;
  bool operator<(const PII o) const { return x == o.x ? y > o.y : x < o.x; }
};

int rt[maxn];
struct SegMentTree {
  int ls[maxn << 5], rs[maxn << 5], cnt = 0;
  PII mx[maxn << 5], zero;

  SegMentTree() { zero.x = zero.y = 0; }

  void ins(int& p, int l, int r, int x) {
    if (!p) p = ++cnt;
    if (l == r) {
      mx[p].x++, mx[p].y = l;
      return;
    }
    int mid = l + r >> 1;
    if (x <= mid)
      ins(ls[p], l, mid, x);
    else
      ins(rs[p], mid + 1, r, x);
    mx[p] = max(mx[ls[p]], mx[rs[p]]);
  }

  int merge(int x, int y, int l, int r) {
    if (!x || !y) return x | y;
    int qwq = ++ cnt;
    if (l == r) {
    	mx[qwq] = mx[x];
      mx[qwq].x += mx[y].x;
      return qwq;
    }
    int mid = l + r >> 1;
    ls[qwq] = merge(ls[x], ls[y], l, mid);
    rs[qwq] = merge(rs[x], rs[y], mid + 1, r);
    mx[qwq] = max(mx[ls[qwq]], mx[rs[qwq]]);
    return qwq;
  }

  PII qry(int p, int a, int b, int l, int r) {
    if (!p) return zero;
    if (a <= l && r <= b) return mx[p];
    int mid = l + r >> 1;
    PII ans = zero;
    if (a <= mid) ans = max(ans, qry(ls[p], a, b, l, mid));
    if (b > mid) ans = max(ans, qry(rs[p], a, b, mid + 1, r));
    return ans;
  }
} smt;

vector<int> g[maxn];
int ed[maxn], mxl[maxn];

struct SAM {
  int ch[maxn][26], fa[maxn], len[maxn];
  int las, cnt;
  SAM() { las = cnt = 1; }

  void ins(int c, int id) {
    int p = las, np = las = ++cnt;
    smt.ins(rt[np], 1, n, id);
    len[np] = len[p] + 1;
    for (; p && !ch[p][c]; p = fa[p]) ch[p][c] = np;
    if (!p) {
      fa[np] = 1;
    } else {
      int q = ch[p][c];
      if (len[q] == len[p] + 1) {
        fa[np] = q;
      } else {
        int nq = ++cnt;
        memcpy(ch[nq], ch[q], sizeof(ch[q]));
        fa[nq] = fa[q], fa[q] = fa[np] = nq, len[nq] = len[p] + 1;
        for (; p && ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
      }
    }
  }

  void build() {
    for (int i = 2; i <= cnt; i++) g[fa[i]].pb(i);
  }

  void init() {
    int p = 1, l = 0, now = 0;
    char* cur = s;
    while (*cur) {
      ++now;
      int c = (*cur++) - ‘a‘;
      while (p && !ch[p][c]) p = fa[p], l = len[p];
      if (ch[p][c]) {
        p = ch[p][c], ++l;
      } else {
        p = 1, l = 0;
      }
      ed[now] = p, mxl[now] = l;
    }
  }
} sam;

int fa[maxn][18];
void dfs(int u) {
  for (int v : g[u]) {
    fa[v][0] = u;
    dfs(v);
    rt[u] = smt.merge(rt[u], rt[v], 1, n);
  }
}

PII qry(int l, int r, int pl, int pr) {
  if (pr - pl + 1 > mxl[pr]) return { l, 0 };
  int p = ed[pr];
  for (int i = 17; ~i; i--)
    if (fa[p][i] && sam.len[fa[p][i]] >= pr - pl + 1) p = fa[p][i];
  PII ans = smt.qry(rt[p], l, r, 1, n);
  if (!ans.x) ans.y = l;
  return { ans.y, ans.x };
}

signed main() {
  // code begin.
  scanf("%s", s), scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    sam.las = 1, scanf("%s", buf);
    char* cur = buf;
    while (*cur) sam.ins((*cur++) - ‘a‘, i);
  }
  sam.build(), sam.init(), dfs(1);
  for (int j = 1; j <= 17; j++)
    for (int i = 1; i <= sam.cnt; i++) fa[i][j] = fa[fa[i][j - 1]][j - 1];
  int _;
  scanf("%d", &_);
  while (_--) {
    int l, r, pl, pr;
    scanf("%d %d %d %d", &l, &r, &pl, &pr);
    PII ans = qry(l, r, pl, pr);
    printf("%d %d\n", ans.x, ans.y);
  }
  return 0;
  // code end.
}

原文地址:https://www.cnblogs.com/Isaunoya/p/12549379.html

时间: 2024-08-29 22:06:29

CF666E Forensic Examination [后缀自动机,线段树合并]的相关文章

CF666E Forensic Examination(后缀自动机+线段树合并)

给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串数组建出后缀自动机,然后我们可以通过跳trans边找到S前i个字符代表的前缀的最长后缀.我们要找的是S[pl..pr]并不是以pr结束最长的后缀,但我们可以确定S[pl..pr]一定是当前点的祖先所以当我们跳到pr代表的点时我们倍增往上跳知道找到一个点的长度刚好大于等于pr-pl+1,这个点就是询问

CF666E Forensic Examination(广义后缀自动机+线段树合并)

Luogu 给你一个串 $ S $ 以及一个字符串数组 $ T_1 ~ T_m $ , $ q $ 次询问,每次问 $ S $ 的子串S[p_l,p_r]在 $ T_l ~ T_r $ 中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 题解时间 SAM的毒瘤题,无论是倍增来满足长度限制,线段树合并来求区间询问,应有尽有... 对于 $ T $ 串建广义SAM,之后考虑如何使得 $ S $ 在SAM上匹配时求出 $ S $ 在每个 $ T $ 的出现次数. 很明显用线段树

HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)

题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点,求第k大的下标即可. 1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=1e5+10,mod=998244353; 5 char buf[N]; 6 int s[N],sa[

CF700E:Cool Slogans(后缀自动机,线段树合并)

Description 给你一个字符串,如果一个串包含两个不重叠的相同子串,那么这个串的价值就是子串的价值+1.问你给定字符串的最大价值子串的价值. Input 第一行读入字符串长度$n$,第二行是字符串. Output 一行答案. Sample Input1 3abc Sample Output1 1 Sample Input2 5ddddd Sample Output2 5 Sample Input3 11abracadabra Sample Output3 3 Solution 首先把后缀

bzoj 3413: 匹配 后缀自动机+线段树合并

并不是很难啊,把细节想好了再写就很轻松了~ code: #include <bits/stdc++.h> #define N 200003 #define LL long long #define setIO(s) freopen(s".in","r",stdin) ,freopen(s".out","w",stdout) using namespace std; struct SAM { int tot,last

CF.666E.Forensic Examination(广义后缀自动机 线段树合并)

题目链接 \(Description\) 给定串S和m个串Ti.Q次询问,每次询问l,r,pl,pr,求S[pl~pr]在Tl~Tr中的哪个串出现次数最多,输出最多次数及其T的下标.若有多个,输出下标最小的. \(Solution\) 挺好的题吧 对T个串建SAM,然后要求出SAM每个节点上|right|最大的是哪个串. 每个节点的|right|可以在DFS parent树时合并子节点得到,如果用线段树维护,|right|最大的位置也可以合并得到. 这样可以离线处理询问,最后DFS一遍得到答案.

【XSY1551】往事 广义后缀数组 线段树合并

题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\)最长公共前缀,\(LCS=\)最长公共后缀 \(1\leq n\leq 200000\),字符集为\(\{0\ldots 300\}\) 题解 我们先看看这个\(LCP(s_u,s_v)\)怎么求 广义后缀自动机不行.广义后缀树可能可以,但我不会.广义后缀数组可以.然后我就开始手推广义后缀数组 广义

[BZOJ1396]识别子串 后缀自动机+线段树

1396: 识别子串 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 451  Solved: 290[Submit][Status][Discuss] Description Input 一行,一个由小写字母组成的字符串S,长度不超过10^5 Output L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. Sample Input agoodcookcooksgoodfood Sample Output 1 2 3 3

BZOJ1396&amp;2865 识别子串 【后缀自动机 + 线段树】

题目 输入格式 一行,一个由小写字母组成的字符串S,长度不超过10^5 输出格式 L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长. 输入样例 agoodcookcooksgoodfood 输出样例 1 2 3 3 2 2 3 3 2 2 3 3 2 1 2 3 3 2 1 2 3 4 题解 BZOJ AC200纪念,, 这两题题干是一样的,但唯一不同的是..后者卡空间[MLE得飞起] 先说解法: 我们知道后缀自动机上的parent树的每个节点子树中叶子的数量就是该节点