JZOJ4774 【GDOI2017模拟9.10】子串 线段树合并维护SAM的fail树信息(CF 666E类似)

来源:互联网 发布:淘宝苹果id代充 编辑:程序博客网 时间:2024/05/21 06:55

题目大意

N个字符串,S1,S2...SN。现有有Q个形如(li,ri,Pi)的询问,表示字符串PiSl~Sr中多少个串出现过。

N,Q5105

解题思路

看到这种多串匹配为问题,我们可以考虑用SAM来实现。首先,我们对N个串Si建一颗Trie,在Trie上构SAM,那么每次我们询问一个串Pi时,我们可以找到这个串在SAM中对应的节点(如果没有答案就是0)。然后就是要考虑怎么统计有多少个Si会有这个节点代表的状态。

我们对于每个Si的后缀,在代表这个字符串的节点上打一个i的标记,构完SAM后沿fail链把标记上传,现在对于一个节点,我们就要询问它有多少个在li~ri中有多少个不同的标记。这个经典问题可以用线段树和并在O(NLogN)的时间完成。

注意:
1. TrieSAM要用Bfs来构图复杂度才是对的。
2. 如果不构Trie,不能加入完一个串直接把Last=Root,要在两个串之间加入一个字符集意外的字符,但是这样SAM的点集大小就要乘2(在这题空间限制如果是512M的话就会被卡)。

程序

//YxuanwKeith#include <cstring>#include <cstdio>#include <algorithm>#include <vector>using namespace std;const int MAXN = 1e6 + 5;struct Sam {    int Len, Root, Pre, Go[3];} A[MAXN];struct Trie {    int Go[2], Root;} Tri[MAXN];struct Tree {    int l, r, Sum;} Tr[MAXN * 20];struct Query {    int l, r, bel;    Query(int a, int b, int c) {l = a, r = b, bel = c;}    Query() {}};vector<Query> Q[MAXN];int Lst, lst, Sum, num, Cnt, tot, QQ, N, Root, Get, Side, Ans[MAXN], Ord[MAXN];char S[MAXN];void Read(int &x) {    char ch = getchar();    while (ch < '0' || ch > '9') ch = getchar();    x = 0;    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();}void Sam_Push(int l, int r, int bel, int len) {    if (Get < len) return;    Q[Side].push_back(Query(l, r, bel));}void Sam_Go(int c) {    for (; lst != Root && !A[lst].Go[c]; lst = A[lst].Pre, Get = A[lst].Len);    if (A[lst].Go[c]) lst = A[lst].Go[c], Get ++;    Side = lst;}void Prepare() {    for (int i = 1; i <= QQ; i ++) {        int l, r;        Read(l), Read(r);        scanf("%s", S + 1);        lst = Root, Get = Side = 0;        int Len = strlen(S + 1);        for (int j = 1; j <= Len; j ++) Sam_Go(S[j] - 'a');        Sam_Push(l, r, i, Len);    }}int Sam_Add(int lst, int c, int Rt) {    int np = ++ tot, p = lst;    A[np].Len = A[lst].Len + 1;    A[np].Root = Rt;        for (; p && !A[p].Go[c]; p = A[p].Pre) A[p].Go[c] = np;    if (!p) A[np].Pre = Root; else {        int q = A[p].Go[c];        if (A[q].Len == A[p].Len + 1) A[np].Pre = q; else {            int nq = ++ tot;            A[nq] = A[q], A[nq].Root = 0;            A[nq].Len = A[p].Len + 1;            A[np].Pre = A[q].Pre = nq;            for (; p && A[p].Go[c] == q; p = A[p].Pre) A[p].Go[c] = nq;        }    }    return np;}void Sort() {    static int tax[MAXN];    memset(tax, 0, sizeof tax);    for (int i = 1; i <= tot; i ++) tax[A[i].Len] ++;    for (int i = 1; i <= tot; i ++) tax[i] += tax[i - 1];    for (int i = tot; i; i --) Ord[tax[A[i].Len] --] = i; }void Tr_Merge(int &Rt, int rt, int ot, int l, int r) {    Rt = ++ Cnt;    if (!rt) {Tr[Rt] = Tr[ot]; return;}    if (!ot) {Tr[Rt] = Tr[rt]; return;}    if (l == r) {        Tr[Rt].Sum = max(Tr[ot].Sum, Tr[rt].Sum);        return;    }       int Mid = (l + r) >> 1;    Tr_Merge(Tr[Rt].l, Tr[rt].l, Tr[ot].l, l, Mid), Tr_Merge(Tr[Rt].r, Tr[rt].r, Tr[ot].r, Mid + 1, r);    Tr[Rt].Sum = Tr[Tr[Rt].l].Sum + Tr[Tr[Rt].r].Sum;   }void Tr_Add(int &Rt, int rt, int l, int r, int Side) {    Rt = ++ Cnt;    if (rt) Tr[Rt] = Tr[rt];    if (l == r) {        Tr[Rt].Sum = 1;        return;    }    int Mid = (l + r) >> 1;    if (Side <= Mid) Tr_Add(Tr[Rt].l, Tr[Rt].l, l, Mid, Side); else        Tr_Add(Tr[Rt].r, Tr[Rt].r, Mid + 1, r, Side);    Tr[Rt].Sum = Tr[Tr[Rt].l].Sum + Tr[Tr[Rt].r].Sum;}void Tr_Query(int Rt, int l, int r, int lx, int rx) {    if (!Rt) return;    if (rx < l || lx > r) return;    if (l >= lx && r <= rx) {        Sum += Tr[Rt].Sum;        return;    }    int Mid = (l + r) >> 1;    Tr_Query(Tr[Rt].l, l, Mid, lx, rx), Tr_Query(Tr[Rt].r, Mid + 1, r, lx, rx);}void Solve() {    Sort();    for (int i = tot; i; i --) {        int Now = Ord[i];        Tr_Merge(A[A[Now].Pre].Root, A[Now].Root, A[A[Now].Pre].Root, 1, N);    }    for (int i = 1; i <= tot; i ++) {        for (int j = 0; j < Q[i].size(); j ++) {            int l = Q[i][j].l, r = Q[i][j].r, bel = Q[i][j].bel;            Sum = 0;            Tr_Query(A[i].Root, 1, N, l, r);            Ans[bel] = Sum;        }    }    for (int i = 1; i <= QQ; i ++)         printf("%d\n", Ans[i]);}void Trie_Add(int bel, int c) {    if (!Tri[Lst].Go[c]) Tri[Lst].Go[c] = ++ num;    Lst = Tri[Lst].Go[c];    Tr_Add(Tri[Lst].Root, Tri[Lst].Root, 1, N, bel);}void Sam_Build() {    static int D[MAXN][2], top = 1;    D[1][0] = 1, D[1][1] = 1;     for (int i = 1; i <= top; i ++) {        for (int j = 0; j < 2; j ++) {            if (!Tri[D[i][1]].Go[j]) continue;            int v = Tri[D[i][1]].Go[j];            D[++ top][0] = Sam_Add(D[i][0], j, Tri[v].Root);            D[top][1] = v;        }    }}int main() {    scanf("%d%d", &N, &QQ);    Root = tot = lst = Lst = num = 1;    for (int i = 1; i <= N; i ++) {        scanf("\n%s", S + 1);        int Len = strlen(S + 1);        Lst = 1;        for (int j = 1; j <= Len; j ++) Trie_Add(i, S[j] - 'a');    }    Sam_Build();    Prepare();    Solve();}
1 0