Loading

【题解】P6292 区间本质不同子串个数

思路

SAM + LCT.

子串计数大概率需要一个 SAM.

首先这个问题和区间数颜色很类似,回忆一下怎么数颜色。

一种常见的方法是考虑扫描线。令 \(p(i)\) 为第 \(i\) 种颜色最后一次出现的位置,则 \([l, r]\) 的答案为 \(\sum\limits [p(i) \geq l]\)。用数据结构动态维护就行。

这个题同样考虑扫描线,不过维护的是子串开头最后一次出现的位置。考虑把询问离线下来,从前往后加入字符。

加入下标 \(x\) 会导致 \([1, x]\) 的所有后缀最后出现的位置改变,由 parent tree 易知这里的后缀等价于 parent tree 上一条从根到某个结点的路径。

于是只需要考虑把这条路径原本的贡献去掉,再加入新的贡献。

这里的操作类似于 LCT 的 access,不妨用 LCT 维护 parent tree 的结构。一种暴力的思路是直接在 LCT 上暴力跳,但是复杂度是假的。

但是我们发现这条路径是连续的,所以可以在 access 的时候顺便做一下。

LCT 不能维护贡献,另外再搞一棵线段树,维护以每个位置为开头的子串数量。因为 parent tree 知,这里路径上的子串长度是连续的。在固定右端点的情况下,它们的开头显然也是一段连续的区间。所以只需要在线段树上大力修改就行。

时间复杂度 \(O(n \log^2 n + m \log n)\)

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;

const int maxn = 1e5 + 5;
const int maxq = 2e5 + 5;
const int sam_sz = maxn << 1;
const int sgt_sz = maxn << 2;

int n, q;
int pos[maxn];
ll ans[maxq];
char s[maxn];

namespace SAM
{
    int lst = 1, cur = 1;
    int len[sam_sz], fa[sam_sz], son[sam_sz][26];

    void cpy_node(int to, int from)
    {
        len[to] = len[from], fa[to] = fa[from];
        memcpy(son[to], son[from], sizeof(son[to]));
    }

    void insert(int c)
    {
        int p = lst, np = lst = ++cur;
        len[np] = len[p] + 1;
        for ( ; p  && (!son[p][c]); p = fa[p]) son[p][c] = np;
        if (!p) fa[np] = 1;
        else
        {
            int q = son[p][c];
            if (len[q] == len[p] + 1) fa[np] = q;
            else
            {
                int nq = ++cur;
                cpy_node(nq, q), len[nq] = len[p] + 1;
                fa[q] = fa[np] = nq;
                for ( ; p && (son[p][c] == q); p = fa[p]) son[p][c] = nq;
            }
        }
    }

    void build()
    {
        for (int i = 1; i <= n; i++)
        {
            insert(s[i] - 'a');
            pos[i] = lst;
        }
    }
}

namespace SGT
{
    ll sum[sgt_sz], lazy[sgt_sz];

    void push_up(int k) { sum[k] = sum[k << 1] + sum[k << 1 | 1]; }

    void push_down(int k, int l, int r)
    {
        if (!lazy[k]) return;
        int mid = (l + r) >> 1;
        sum[k << 1] += 1ll * (mid - l + 1) * lazy[k];
        sum[k << 1 | 1] += 1ll * (r - mid) * lazy[k];
        lazy[k << 1] += lazy[k], lazy[k << 1 | 1] += lazy[k];
        lazy[k] = 0ll;
    }

    void update(int k, int l, int r, int ql, int qr, int w)
    {
        if ((l >= ql) && (r <= qr))
        {
            sum[k] += 1ll * (r - l + 1) * w, lazy[k] += w;
            return;
        }
        push_down(k, l, r);
        int mid = (l + r) >> 1;
        if (ql <= mid) update(k << 1, l, mid, ql, qr, w);
        if (qr > mid) update(k << 1 | 1, mid + 1, r, ql, qr, w);
        push_up(k);
    }

    ll query(int k, int l, int r, int ql, int qr)
    {
        if ((l >= ql) && (r <= qr)) return sum[k];
        push_down(k, l, r);
        int mid = (l + r) >> 1;
        ll res = 0;
        if (ql <= mid) res += query(k << 1, l, mid, ql, qr);
        if (qr > mid) res += query(k << 1 | 1, mid + 1, r, ql, qr);
        return res;
    }
}

namespace LCT
{
    #define ls son[x][0]
    #define rs son[x][1]

    int fa[sam_sz], son[sam_sz][2];
    int top, q[sam_sz], val[sam_sz], lazy[sam_sz];
    bool rev[sam_sz];

    bool get(int x) { return (son[fa[x]][1] == x); }

    bool is_root(int x) { return (son[fa[x]][0] != x) && (son[fa[x]][1] != x); }

    void push_down(int x)
    {
        if (!lazy[x]) return;
        if (ls) val[ls] = lazy[ls] = lazy[x];
        if (rs) val[rs] = lazy[rs] = lazy[x];
        lazy[x] = 0;
    }

    void rotate(int x)
    {
        int y = fa[x], z = fa[y], k = get(x);
        son[y][k] = son[x][k ^ 1], fa[son[x][k ^ 1]] = y;
        son[x][k ^ 1] = y;
        if (!is_root(y)) son[z][son[z][1] == y] = x;
        fa[x] = z, fa[y] = x;
    }

    void splay(int x)
    {
        q[top = 1] = x;
        for (int i = x; !is_root(i); i = fa[i]) q[++top] = fa[i];
        for (int i = top; i; i--) push_down(q[i]);
        while (!is_root(x))
        {
            int y = fa[x], z = fa[y];
            if (!is_root(y)) rotate(get(x) == get(y) ? y : x);
            rotate(x);
        }
    }

    void access(int x, int cp)
    {
        int nd = 0;
        for (int t = 0; x; t = x, x = fa[x])
        {
            splay(x);
            if (int p = val[x]) SGT::update(1, 1, n, p - SAM::len[x] + 1, p - SAM::len[fa[x]], -1);
            rs = t, nd = x;
        }
        val[nd] = lazy[nd] = cp;
        SGT::update(1, 1, n, 1, cp, 1);
    }

    void build() { for (int i = 2; i <= SAM::cur; i++) fa[i] = SAM::fa[i]; }
}

struct Query
{
    int l, r, idx;

    bool operator < (const Query& rhs) const { return (r < rhs.r); }
} qry[maxq];

int main()
{
    scanf("%s%d", s + 1, &q);
    n = strlen(s + 1);
    SAM::build();
    LCT::build();
    for (int i = 1; i <= q; i++)
    {
        qry[i].idx = i;
        scanf("%d%d", &qry[i].l, &qry[i].r);
    }
    sort(qry + 1, qry + q + 1);
    for (int i = 1, cur = 1; i <= q; i++)
    {
        while (cur <= qry[i].r) LCT::access(pos[cur], cur), cur++;
        ans[qry[i].idx] = SGT::query(1, 1, n, qry[i].l, qry[i].r);
    }
    for (int i = 1; i <= q; i++) printf("%lld\n", ans[i]);
    return 0;
}
posted @ 2023-01-13 14:48  kymru  阅读(59)  评论(0编辑  收藏  举报