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

题目大意

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

N,Q5105

解题思路

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

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

注意:
1. Trie SAM 要用 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();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值