CF666E Forensic Examination

\(CF666E\ \ Forensic\ Examination\)

题意

给定母串 \(s\)\(m\) 个文本串 \(t_1,t_2,\dots t_m\),给出 \(q\) 次询问,每次 \(st,ed,ql,qr\),回答从 \(t_{st}-t_{ed}\) 中出现子串 \(s[ql,qr]\) 次数最多的文本串编号和出现次数。

前置知识

广义后缀自动机 \((GSAM)\)
动态开点线段树+线段树合并

思路分析

对于 \(m\) 个串我们显然要扔进 \(GSAM\) 里,然后我们维护每个节点的 \(siz\)\(siz[i][id]\) 为节点 \(i\) 在串 \(id\) 上的 \(endpos\) 大小。我们可以先做一道水题找相同字符[P3181]熟悉 \(siz\)

  • \(P3181\) 我们考虑使用 \(siz[i][1]\)\(siz[i][2]\) 维护在第一个串和第二个串上的 \(siz\),然后和单串 \(SAM\) 一样,在 \(parent\ tree\) 上进行子树求和维护 \(siz\).最后答案为 $ \sum siz[i][1]\times siz[i][2]\times (len[i]-len[link[i]]) $
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define read read()
#define pt puts("")
inline int read
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(ll x)
{
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
const int N = 5e5+10;
char s[N];
ll siz[N<<1][3];
vector<int >son[N<<1];
ll ans;

struct G_Suffix_Auto_Mation{
    int tot;int last;
    int link[N<<1],len[N<<1];
    int ch[N<<1][27];

    void insert(int c,int id)
    {
        int p=last;
        if(ch[p][c]){
            int q=ch[p][c];
            if(len[p]+1==len[q]){
                siz[q][id]=1;
                last=q;return;
            }
            else{
                int copy=++tot;
                len[copy]=len[p]+1;
                link[copy]=link[q];
                for(int i=1;i<=26;i++)  ch[copy][i]=ch[q][i];
                while(p!=-1&&ch[p][c]==q){
                    ch[p][c]=copy;
                    p=link[p];
                }
                link[q]=copy;
                siz[copy][id]=1;
                last=copy;
                return;
            }
        }
        int cur=++tot;
        len[cur]=len[p]+1;
        while(p!=-1&&!ch[p][c]){
            ch[p][c]=cur;
            p=link[p];
        }
        if(p==-1)  link[cur]=0;
        else{
            int q=ch[p][c];
            if(len[p]+1==len[q])  link[cur]=q;
            else{
                int copy=++tot;
                len[copy]=len[p]+1;
                link[copy]=link[q];
                for(int i=1;i<=26;i++)  ch[copy][i]=ch[q][i];
                while(p!=-1&&ch[p][c]==q){
                    ch[p][c]=copy;
                    p=link[p];
                }
                link[cur]=link[q]=copy;
            }
        }
        siz[cur][id]=1;
        last=cur;
        return;
    }
    // void out(int x){
    //     for(int i=1;i<=26;i++)  if(ch[x][i])  cout<<x<<' '<<ch[x][i]<<' ',putchar(i+'a'-1),pt,out(ch[x][i]);
    // }
    void makeparent()
    {
        for(int i=1;i<=tot;i++){
            son[link[i]].push_back(i);
        }
    }
    void dfs(int x){
        for(int y:son[x]){
            dfs(y);
            siz[x][1]+=siz[y][1];
            siz[x][2]+=siz[y][2];
        }
    }
    void solve(){
        for(int i=1;i<=tot;i++)
            ans+=siz[i][1]*siz[i][2]*(len[i]-len[link[i]]);
        write(ans);
    }

}gsam;
signed main()
{
    gsam.link[0]=-1;
    scanf(" %s",s+1);int m=strlen(s+1);
    for(int i=1;i<=m;++i)  gsam.insert(s[i]-'a'+1,1);
    gsam.last=0;
    scanf(" %s",s+1);m=strlen(s+1);
    for(int i=1;i<=m;++i)  gsam.insert(s[i]-'a'+1,2);
    gsam.makeparent();
    gsam.dfs(0);
    gsam.solve();
    return 0;
}

好那么回到这个题,它有 \(5\times 10^4\) 个串,我们当然不能和上文一样维护一个二维数组 \(siz\),时间和空间都不允许,那么我们考虑将 \(siz\) 转到动态开点线段树上去并通过线段树合并维护 \(siz\) 即可。

具体地,我们先建好文本串的 \(GSAM\) ,每个节点(显然对应一个等价类)开一棵 \([1,m]\) 线段树,最后把母串塞进去,同时记录每一位对应的节点,这样方便查询。然后遍历 \(parent\ tree\),将子树合并(这相当于子树求和)。预处理好后开始查询,查询包含 \(s[ql,qr]\) 段对应的等价类,这可以通过从 \(qr\) 倍增上跳实现。最后查询该节点 \([st,ed]\) 中最大值即可。

\(AC\ \ Code\)

#include<bits/stdc++.h>
using namespace std;
#define read read()
#define pt puts("")
inline int read
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-')  f=-1;c=getchar();}
    while(c>='0'&&c<='9')   x=(x<<3)+(x<<1)+c-'0',c=getchar();
    return f*x;
}
void write(int x)
{
    if(x<0)  putchar('-'),x=-x;
    if(x>9)  write(x/10);
    putchar(x%10+'0');
    return;
}
const int N = 5e5+10;
const int M = 5e4+10;
#define NM (N+M)<<1
char s[N],t[M];
int m;int n;
int last;
int pos[NM];
int fa[NM][21],dep[NM];
vector<int >son[NM];
int root[NM];
struct SPX{
    int num,id;
    SPX(int Num=0,int Id=0){num=0,id=0;}
    bool operator > (const SPX &spx)const{return num==spx.num?(id<spx.id):(num>spx.num);}
};
SPX MAX(SPX a,SPX b){
    if(a.num==b.num)  return (a.id<b.id?a:b);
    return (a.num>b.num?a:b);
}
namespace Segment_Tree{
    int sum;
    struct Seg{
        int ls,rs;
        SPX ans;
        #define ls(i) tr[i].ls
        #define rs(i) tr[i].rs 
        #define ans(i)  tr[i].ans
    }tr[NM<<1];
    void pushup(int i){
        ans(i)=MAX(ans(ls(i)),ans(rs(i)));
    }
    void join(int &i,int l,int r,int x)//动态开点线段树
    {
        if(!i)  i=++sum;
        if(l==r){
            ++ans(i).num;
            ans(i).id=l;
            return;
        }
        int mid=(l+r)>>1;
        if(x<=mid)  join(ls(i),l,mid,x);
        else  join(rs(i),mid+1,r,x);
        pushup(i);
        return;
    }
    int merge(int x,int y,int l,int r){//线段树合并板子
        if(!x||!y)  return x+y;
        int t=++sum;
        if(l==r){
            tr[t]=tr[x];ans(t).num=ans(x).num+ans(y).num;
            return t;
        }
        int mid=(l+r)>>1;
        ls(t)=merge(ls(x),ls(y),l,mid);
        rs(t)=merge(rs(x),rs(y),mid+1,r);
        pushup(t);
        return t;
    }
    SPX ask(int i,int ql,int qr,int l,int r){
        if(!i)  return SPX(0,m+1);
        if(ql<=l&&r<=qr)  return ans(i);
        int mid=(l+r)>>1;
        SPX res=(0,m+1);
        if(ql<=mid)  res=MAX(res,ask(ls(i),ql,qr,l,mid));
        if(qr>mid)  res=MAX(res,ask(rs(i),ql,qr,mid+1,r));
        return res;
    }
}using namespace Segment_Tree;

void dfs(int x){
    for(int j=1;(1<<j)<=dep[x];j++)  fa[x][j]=fa[fa[x][j-1]][j-1];
    for(int y:son[x]){
        dep[y]=dep[x]+1;fa[y][0]=x;
        dfs(y);
        root[x]=merge(root[x],root[y],1,m);//子树"求和"
    }
}
struct G_Suffix_Auto_mation{
    int tot;
    int link[NM],len[NM];
    int ch[NM][27];
    int insert(int c,int id)//dfs在线建GSAM板子
    {
        int p=last;
        if(ch[p][c]){
            int q=ch[p][c];
            if(len[p]+1==len[q]){
                if(id)  join(root[q],1,m,id);
                return q;
            }
            else{
                int copy=++tot;
                len[copy]=len[p]+1;
                link[copy]=link[q];
                for(int i=1;i<=26;i++)  ch[copy][i]=ch[q][i];
                while(p!=-1&&ch[p][c]==q){
                    ch[p][c]=copy;
                    p=link[p];
                }
                link[q]=copy;
                if(id)  join(root[copy],1,m,id);
                return copy;
            }
        }
        int cur=++tot;
        len[cur]=len[p]+1;
        while(p!=-1&&!ch[p][c]){
            ch[p][c]=cur;
            p=link[p];
        }
        if(p==-1)  link[cur]=0;
        else{
            int q=ch[p][c];
            if(len[p]+1==len[q])  link[cur]=q;
            else{
                int copy=++tot;
                len[copy]=len[p]+1;
                link[copy]=link[q];
                for(int i=1;i<=26;i++)  ch[copy][i]=ch[q][i];
                while(p!=-1&&ch[p][c]==q){
                    ch[p][c]=copy;
                    p=link[p];
                }
                link[cur]=link[q]=copy;
            }
        }
        if(id)  join(root[cur],1,m,id);
        return cur;
    }
    void makeparent(){
        for(int i=1;i<=tot;i++)  son[link[i]].push_back(i);//建parent tree
        dep[0]=1;dfs(0);
    }
    int find(int u,int qlen){
        u=pos[u];
        for(int i=20;i>=0;i--)//倍增查找
            if(fa[u][i] && len[fa[u][i]]>=qlen)  u=fa[u][i];
        return u;
    }
    void solve()
    {
        int st,ed,ql,qr;
        st=read,ed=read,ql=read,qr=read;
        int rt=root[find(qr,qr-ql+1)];//查询等价类
        SPX spx=ask(rt,st,ed,1,m);
        if(!spx.num)  spx.id=st;
        write(spx.id);putchar(' ');write(spx.num);pt;
    }
}gsam;
signed main()
{
    gsam.link[0]=-1;
    scanf(" %s",s+1);n=strlen(s+1);
    m=read;
    for(int i=1;i<=m;i++){
        scanf(" %s",t+1);last=0;
        for(int j=1;t[j];j++)  last=gsam.insert(t[j]-'a'+1,i);
    }
    last=0;
    for(int i=1;i<=n;i++)  pos[i]=last=gsam.insert(s[i]-'a'+1,0);
    gsam.makeparent();
    int T;T=read;while(T--)  gsam.solve();
    return 0;
}
posted @ 2024-04-19 08:24  lty_ylzsx  阅读(9)  评论(0编辑  收藏  举报