bzoj5417&&luogu4770你的名字 后缀自动机+线段树合并

bzoj5417&&luogu4770你的名字

题目传送门:
洛谷
bzoj

分析

题目大意:
给定一个模板串 S S S,每次给定一个字符串 T T T l , r l,r l,r,求 T T T中有多少个本质不同的子串无法匹配 S S S的子串 S [ l ⋯ r ] S[l\cdots r] S[lr]
首先肯定考虑的是 l = 1 , r = S l=1,r=S l=1,r=S的情况。
分析 T T T的每一个前缀 T [ 1 ⋯ i ] T[1\cdots i] T[1i]
肯定存在一个分界点 x x x,使得这个前缀 T [ 1 ⋯ i ] T[1\cdots i] T[1i]的后缀有
∀ x ≥ j , T [ x ⋯ i ] \forall x\ge j,T[x\cdots i] xj,T[xi]可以匹配 S S S
∀ x &lt; j , T [ x ⋯ i ] \forall x &lt; j,T[x\cdots i] x<j,T[xi]无法匹配 S S S
并且这个 j j j是单调递增的,我们令 l i m i = i − j + 1 lim_i=i-j+1 limi=ij+1
由于题目中要求的是本质不同的子串,所以我们必须对 T T T建立后缀自动机,对于每一个 T T T上的节点,假设其 R i g h t Right Right集合中最先出现的位置为 p o s pos pos,那么这个节点 v v v的贡献就是
m x [ v ] − max ⁡ { m x [ f a [ v ] ] , l i m [ p o s [ v ] ] } mx[v]-\max \{mx[fa[v]],lim[pos[v]]\} mx[v]max{mx[fa[v]],lim[pos[v]]}
这个式子的意义是,考虑后缀自动机的 p a r e n t parent parent树等价于把原串的所有前缀逆序插入 T r i e Trie Trie后压缩, m x mx mx就是这个节点到根节点的所代表的字符串长度,那么这个节点就压缩了串 m x [ v ] ⋯ m x [ f a [ v ] ] mx[v]\cdots mx[fa[v]] mx[v]mx[fa[v]]中的信息,而 l i m lim lim是表示从某个位置往前没有贡献的最后一个位置。所以产生贡献的长度就是 m x [ v ] − max ⁡ { m x [ f a [ v ] ] , l i m [ p o s [ v ] ] } mx[v]-\max \{mx[fa[v]],lim[pos[v]]\} mx[v]max{mx[fa[v]],lim[pos[v]]}
现在考虑如何求 l i m lim lim
按顺序考虑 T T T的每一个前缀,由于 j j j的单调性质,可以采用类似双指针的方式,随着前缀的挪动,去找到合法的 j j j
由于是匹配 S S S的子串,所以对 S S S另外建立一颗后缀自动机,我们只需要在 S S S上进行匹配。如果新走了某字符 c c c,如果存在 T r a n s ( n o w , c ) Trans(now,c) Trans(now,c),那么就往下走,否则的话就移动 j j j,跳 f a i l fail fail链即可。

现在考虑加上 l , r l,r l,r的限制。
实际上等价于当前节点 n o w now now R i g h t Right Right集合内存在某个位置 p ∈ [ l + i − j + 1 , r ] p\in[l+i-j+1,r] p[l+ij+1,r]
因为你要从当前位置 p p p匹配长度为 i − j + 1 i-j+1 ij+1的串,也就是 l i m lim lim
接下来我们只要求查询 S S S后缀自动机上某个节点 u u u R i g h t Right Right集合中是否含有在某个区间内的数。
这个东西采用线段树合并或者主席树维护子树 D f s Dfs Dfs即可。
因为某个节点的 R i g h t Right Right集合就是其 p a r e n t parent parent树上儿子的 R i g h t Right Right集合的并。

代码

采用线段树合并

#include<bits/stdc++.h>
#define re(x) std::memset(x, 0, sizeof(x))
const int N = 1e6 + 10;
int ri() {
    char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int ps[N], rt[N], c[N], sa[N], lim[N], n;
struct Segment {
    int ls[N * 20], rs[N * 20], top;
    void Modify(int &p, int L, int R, int x) {
        p = ++top; if(L == R) return ; int m = L + R >> 1;
        x <= m ? Modify(ls[p], L, m, x) : Modify(rs[p], m + 1, R, x);
    }
    int Merge(int u, int v) {
        if(!u || !v) return u | v;
        int np = ++top;
        ls[np] = Merge(ls[u], ls[v]);
        rs[np] = Merge(rs[u], rs[v]);
        return np;
    }
    bool Que(int p, int L, int R, int st, int ed) {
        if(!p || st > ed) return false;
        if(L == st && ed == R) return true;
        int m = L + R >> 1; bool r = false;
        if(st <= m) r |= Que(ls[p], L, m, st, std::min(ed, m));
        if(ed > m) r |= Que(rs[p], m + 1, R, std::max(m + 1, st), ed);
        return r;
    }
}seg;
struct SAM {
    int ch[N][26], mx[N], fa[N], last, top;
    void Init() {re(ch[1]); last = top = 1;}
    int New(int x) {return mx[++top] = x, re(ch[top]), fa[top] = 0, top;}
    void Extend(int c, int x, bool e) {
        int p = last, np = last = New(mx[p] + 1); if(e) ps[np] = x;
        for(;p && !ch[p][c]; p = fa[p]) ch[p][c] = np;
        if(!p) fa[np] = 1;
        else {
            int q = ch[p][c];
            if(mx[q] == mx[p] + 1) fa[np] = q;
            else {
                int nq = New(mx[p] + 1);
                memcpy(ch[nq], ch[q], sizeof(ch[nq]));
                fa[nq] = fa[q]; if(e) ps[nq] = x;
                fa[q] = fa[np] = nq;
                for(;ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
            }
        }
        if(!e) seg.Modify(rt[np], 1, n, x);
    }
    void Work() {
        for(int i = 1;i <= top; ++i) ++c[mx[i]];
        for(int i = 1;i <= n; ++i) c[i] += c[i - 1];
        for(int i = 1;i <= top; ++i) sa[c[mx[i]]--] = i;
        for(int i = top; i; --i)
            rt[fa[sa[i]]] = seg.Merge(rt[fa[sa[i]]], rt[sa[i]]);
    }
}S, T;
void rc(int *s, int &n, SAM &S, bool e) {
    S.Init(); char c = getchar(); for(;c < 'a' || c > 'z'; c = getchar()) ; n = 0;
    for(;c >= 'a' && c <= 'z'; c = getchar()) s[++n] = c - 'a'; 
    for(int i = 1;i <= n; ++i) S.Extend(s[i], i, e);
}
int s[N];
int main() {
    rc(s, n, S, 0); S.Work();
    for(int X = ri(), m;X--;) {
        rc(s, m, T, 1); int l = ri(), r = ri(), tot = 0;
        for(int i = 1, su = 1;i <= m; ++i) {
            int c = s[i];
            for(;su && !(S.ch[su][c] && seg.Que(rt[S.ch[su][c]], 1, n, l + tot, r));) {
                if(!tot) {su = 0; break;}
                if(--tot == S.mx[S.fa[su]]) su = S.fa[su];
            }
            if(!su) su = 1, tot = 0;
            else ++tot, su = S.ch[su][c];
            lim[i] = tot;
        }
        long long Ans = 0;
        for(int i = 2;i <= T.top; ++i)
            Ans += std::max(0, T.mx[i] - std::max(T.mx[T.fa[i]], lim[ps[i]]));
        printf("%lld\n", Ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值