51Nod 算法马拉松17 Simple KMP 链剖维护SAM的fail树

题目大意

化简一下题意!
现在给你一个长度为 N 的字符串,对于每个字符i,我们要统计 Ansi 为前 i 个字符有多少个相同的子串,并且其中一个子串一定要经过位置i。在第 i 行输出jj=1Ansj

N100000

解题思路

我们考虑先对原串建SAM,问题就变成了统计一个前缀所在节点的 fail 链上有多少 right 是小于 i 的,我们考虑对fail树链剖,每次统计完位置 i 的答案后,就把right=i的贡献加上。即把 fail 链上的每个点加上 A[i].lenA[A[i].fail].len 的权值。而对于查询,直接统计到根路径上的权值和就可以了。

程序

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 2e5 + 5;
const int Mo = 1e9 + 7;

struct SAM {
    int fail, Len, Go[26];
} A[MAXN];

struct Tree {
    int tag, Sum, Num;
} Tr[MAXN * 4];

char S[MAXN];
int N, Root, tot, Lst, time;
int Cnt, Last[MAXN], Go[MAXN * 2], Next[MAXN * 2];
int Size[MAXN], MSon[MAXN], Top[MAXN], Ord[MAXN], Bel[MAXN], Pre[MAXN];


void Link(int u, int v) {
    Next[++ Cnt] = Last[u], Last[u] = Cnt, Go[Cnt] = v;
}

void Add(int ch) {
    int np = ++ tot, p = Lst;
    A[np].Len = A[p].Len + 1;
    for (; p && !A[p].Go[ch]; p = A[p].fail) A[p].Go[ch] = np;
    if (!p) A[np].fail = Root; else {
        int q = A[p].Go[ch];
        if (A[q].Len == A[p].Len + 1) A[np].fail = q; else {
            int nq = ++ tot;
            A[nq] = A[q];
            A[nq].Len = A[p].Len + 1;
            A[q].fail = A[np].fail = nq;
            for (; p && A[p].Go[ch] == q; p = A[p].fail) A[p].Go[ch] = nq;
        }
    }
    Lst = np;
}

void Basis(int Now) {
    Size[Now] = 1;
    for (int p = Last[Now]; p; p = Next[p]) {
        int v = Go[p];
        Pre[v] = Now;
        Basis(v);
        Size[Now] += Size[v];
        if (Size[v] > Size[MSon[Now]]) MSon[Now] = v;
    }
}

void Promote(int Now, int top) {
    if (!Now) return;
    Ord[Now] = ++ time;
    Bel[time] = Now; 
    Top[Now] = top;
    Promote(MSon[Now], top);
    for (int p = Last[Now]; p; p = Next[p]) {
        int v = Go[p];
        if (v == MSon[Now]) continue;
        Promote(v, v);
    }
}

void Build(int Now, int l, int r) {
    if (l == r) {
        int p = Bel[l];
        Tr[Now].Num = A[p].Len - A[A[p].fail].Len; 
        return;
    }
    int Mid = (l + r) >> 1;
    Build(Now * 2, l, Mid), Build(Now * 2 + 1, Mid + 1, r);
    Tr[Now].Num = (Tr[Now * 2].Num + Tr[Now * 2 + 1].Num) % Mo;
}

void Prepare() {
    Lst = tot = Root = 1;
    for (int i = 1; i <= N; i ++) Add(S[i] - 'a');
    for (int i = 1; i <= tot; i ++)
        Link(A[i].fail, i);
    Basis(1);
    Promote(1, 1);
    Build(1, 1, tot);
}

void MakeTag(int Now, int tag) {
    Tr[Now].tag = (Tr[Now].tag + tag) % Mo;
    Tr[Now].Sum = (Tr[Now].Sum + tag * 1ll * Tr[Now].Num % Mo) % Mo;
}

void Push(int Now, int l, int r) {
    if (l != r && Tr[Now].tag) {
        MakeTag(Now * 2, Tr[Now].tag);
        MakeTag(Now * 2 + 1, Tr[Now].tag);
    }
    Tr[Now].tag = 0;
}

int Query(int Now, int l, int r, int lx, int rx) {
    if (rx < l || lx > r) return 0;
    Push(Now, l, r);
    if (l >= lx && r <= rx) return Tr[Now].Sum;
    int Mid = (l + r) >> 1;
    return (Query(Now * 2, l, Mid, lx, rx) + Query(Now * 2 + 1, Mid + 1, r, lx, rx)) % Mo;
}

void Modify(int Now, int l, int r, int lx, int rx) {
    if (rx < l || lx > r) return;
    if (l >= lx && r <= rx) {
        MakeTag(Now, 1);
        return;
    }
    int Mid = (l + r) >> 1;
    Modify(Now * 2, l, Mid, lx, rx), Modify(Now * 2 + 1, Mid + 1, r, lx, rx);
    Tr[Now].Sum = (Tr[Now * 2].Sum + Tr[Now * 2 + 1].Sum) % Mo;
}

void Solve() {
    int Ans = 0, Now = 0, p = Root;
    for (int i = 1; i <= N; i ++) {
        p = A[p].Go[S[i] - 'a'];
        for (int P = p; P; P = Pre[Top[P]])
            Now = (Now + Query(1, 1, tot, Ord[Top[P]], Ord[P])) % Mo;
        Ans = (Ans + Now) % Mo;
        printf("%d\n", Ans);
        for (int P = p; P; P = Pre[Top[P]])
            Modify(1, 1, tot, Ord[Top[P]], Ord[P]);
    }
}

int main() {
    scanf("%d", &N);
    scanf("%s", S + 1);

    Prepare();
    Solve();
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值