题解 CF666E【Forensic Examination】

$\text{Link}$

题意

给 $m$ 个文本串 $T_{1,2,...,m}$ 和一个长度为 $n$ 的字符串 $S$,$q$ 次询问,设 $\text{app}(s,t)$ 表示 $s$ 在 $t$ 中的出现次数,求 $\max_{i=pl}^{pr}\text{app}(S[l,r],T_i)$。

$1\le n,q\le 5\times 10^5,1\le m,\sum|T|\le 5\times 10^4$。

思路

$\text{Update }2022.4.26$:增加了部分说明。

注:本题解中的 $l,r$ 和 $pl,pr$的意义与题目中相反。

考虑对于 $m$ 个串建出广义后缀自动机,对于每个节点开一个动态开点权值线段树,下标为所属串的编号,记录区间最大值和下标。对于每个前缀结点,将其所属的字符串的位置 $+1$。

然后建立 $\text{parent}$ 树,对其跑线段树合并,即可得出每个结点对应的在每个串中的出现次数。

预处理出 $S$ 的每个前缀 $S[1,i]$ 在广义 $\text{SAM}$ 中能匹配上的最长后缀的长度以及它匹配到的位置,记为 $ansi_i$ 和 $pos_i$。具体来说,若到下一个字符可转移则转移,令 $ansi_{i+1}=ansi_i+1$,否则跳 $\text{link}$ 直到有转移或者到根。

对于一次查询,先判掉 $r-l+1>ansi_r$ 的无解情况,再考虑从 $cur=pos_r$ 的位置得到 $S[l,r]$ 对应的结点。因为 $\text{parent}$ 树上的祖先都是它的后缀,所以我们在 $\text{parent}$ 树上倍增到 $len_{cur}\ge r-l+1$,此时节点 $cur$ 便包含串 $S[l,r]$,直接在 $cur$ 的线段树上查询 $[pl,pr]$ 的极值即可。

注意线段树合并时需要采取新建节点的方法而不是直接更改,因为合并完之后需要查询任意节点的答案。

这里的倍增是个很经典的技巧。

时间复杂度 $O(\sum |T|\log|T|+q\log n)$。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff

}
const int N=1e6+10;
struct node{
    int val,cnt;
    inline friend bool operator<(const node &a,const node &b){
        return a.cnt==b.cnt?a.val>b.val:a.cnt<b.cnt;
    }
    inline friend node operator+(const node &a, const node &b){
        return (node){a.val,a.cnt+b.cnt};
    }
    node(int x=0,int y=0){val=x,cnt=y;}
};
struct Edge{
    int to,next;
};
int n,m,q;
Edge a[N<<1];
int head[N],dep[N],f[20][N],cnt;
int pos[N],ansi[N];
char str[N];
inline void add(int u,int v){
    cnt++;
    a[cnt].to=v;
    a[cnt].next=head[u];
    head[u]=cnt;
}
struct Segument_Tree{
    int son[2][N*20],root[N],cnt;
    node val[N*20];
    #define ls (son[0][rt])
    #define rs (son[1][rt])
    inline void insert(int &rt,int l,int r,int p){
        if(!rt){
            rt=++cnt;
        }
        if(l==r){
            val[rt].cnt++;
            val[rt].val=l;
            return ;
        }
        int mid=l+r>>1;
        if(p<=mid){
            insert(ls,l,mid,p);
        }else{
            insert(rs,mid+1,r,p);
        }
        val[rt]=max(val[ls],val[rs]);
    }
    inline int merge(int a,int b,int l,int r){
        if(!a||!b) return a+b;
        int rt=++cnt;
        if(l==r){
            val[rt]=val[a]+val[b];
            return rt;
        }
        int mid=l+r>>1;
        ls=merge(son[0][a],son[0][b],l,mid);
        rs=merge(son[1][a],son[1][b],mid+1,r);
        val[rt]=max(val[ls],val[rs]);
        return rt;
    }
    inline node query(int rt,int l,int r,int L,int R){
        if(!rt) return node();
        if(L<=l&&r<=R) return val[rt];
        node ans;
        int mid=l+r>>1;
        if(L<=mid) ans=max(ans,query(ls,l,mid,L,R));
        if(R>mid) ans=max(ans,query(rs,mid+1,r,L,R));
        return ans;
    }
    inline void dfs(int rt){
        for(int i=head[rt];i;i=a[i].next){
            if(a[i].to!=f[0][rt]){
                f[0][a[i].to]=rt;
                dfs(a[i].to);
                root[rt]=merge(root[rt],root[a[i].to],1,m);
            }
        }
    }
}t;
struct SAM{
    struct statu{
        int len,link;
        int ch[26];
    }a[N];
    int siz,last;
    SAM(){
        a[0].len=0;
        a[0].link=-1;
        last=0;
    }
    inline void insert(int c,int x){
        int cur=++siz;
        a[cur].len=a[last].len+1;
        t.insert(t.root[cur],1,m,x);
        int p=last;
        while(p!=-1&&!a[p].ch[c]){
            a[p].ch[c]=cur;
            p=a[p].link;
        }
        if(~p){
            int q=a[p].ch[c];
            if(a[p].len+1==a[q].len){
                a[cur].link=q;
            }else{
                int clone=++siz;
                a[clone]=a[q];
                a[clone].len=a[p].len+1;
                while(p!=-1&&a[p].ch[c]==q){
                    a[p].ch[c]=clone;
                    p=a[p].link;
                }
                a[q].link=a[cur].link=clone;
            }
        }else{
            a[cur].link=0;
        }
        last=cur;
    }
    inline int insert(){
        static char str[N];
        int T=read();
        m=T;
        for(int k=1;k<=T;k++){
            readstr(str);
            int len=strlen(str+1);
            last=0;
            for(int i=1;i<=len;i++){
                insert(str[i]-'a',k);
            }
        }
        return T;
    }
    inline void build(){
        for(int i=1;i<=siz;i++){
            add(a[i].link,i);
        }
    }
    inline void calc(){
        int anss=0;
        int p=1;
        for(int i=1;i<=n;i++){
            int c=str[i]-'a';
            if(a[p].ch[c]){
                anss++;
                p=a[p].ch[c];
            }else{
                while(p!=-1&&!a[p].ch[c]){
                    p=a[p].link;
                }
                if(~p){
                    anss=a[p].len+1;
                    p=a[p].ch[c];
                }else{
                    anss=0;
                    p=1;
                }
            }
            ansi[i]=anss;
            pos[i]=p;
        }
    }
}sam;
int main(){
    n=readstr(str);
    m=sam.insert();
    sam.build();
    t.dfs(0);
    for(int i=1;i<=18;i++){
        for(int j=1;j<=sam.siz;j++){
            f[i][j]=f[i-1][f[i-1][j]];
        }
    }
    sam.calc();
    q=read();
    while(q--){
        int pl=read(),pr=read(),l=read(),r=read();
        int len=r-l+1;
        if(len>ansi[r]){
            write(pl),putc(' '),write(0),putc('\n');
            continue;
        }
        int cur=pos[r];
        for(int i=18;~i;i--){
            if(sam.a[f[i][cur]].len>=len){
                cur=f[i][cur];
            }
        }
        node ans=t.query(t.root[cur],1,m,pl,pr);
        if(ans.cnt==0) ans.val=pl;
        write(ans.val),putc(' '),write(ans.cnt),putc('\n');
    }
    flush();
    return 0;
}

再见 qwq~

posted @ 2021-05-31 15:42  ffffyc  阅读(7)  评论(0)    收藏  举报  来源