CodeForces666E Forensic Examination

题目描述

给你一个串S以及一个字符串数组T[1..m]q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数。

如有多解输出最靠前的那一个。

题解

算是道字符串比较套路的题吧。

对模式串建SAM,对所有模式串的所有前缀维护right集合。

然后对于每个询问,倍增找到关键点,查子树众数。

坑:在最匹配串做匹配的时候,要记录匹配长度,如果匹配长度不够询问长度,直接判无解。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 1000009
using namespace std;
typedef long long ll;
int ch[N][26],tott,tr[N*32],id[N*32],ls[N*32],rs[N*32],ans1,ans2,fa[N],mat[N];
int cnt,last,l[N],tot,head[N],n,p[21][N],deep[N],T[N],tag[N];
char s[N],s1[N];
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
void upd(int &cnt,int l,int r,int x){
    if(!cnt)cnt=++tott;
    if(l==r){tr[cnt]++;id[cnt]=l;return;}
    int mid=(l+r)>>1;
    if(mid>=x)upd(ls[cnt],l,mid,x);
    else upd(rs[cnt],mid+1,r,x);
    tr[cnt]=max(tr[ls[cnt]],tr[rs[cnt]]);
    id[cnt]=tr[ls[cnt]]>=tr[rs[cnt]]?id[ls[cnt]]:id[rs[cnt]];
}
int merge(int x,int y,int l,int r){
    if(!x||!y)return x^y;
    int p=++tott;
    if(l==r){tr[p]=tr[x]+tr[y];id[p]=l;return p;}
    int mid=(l+r)>>1;
    ls[p]=merge(ls[x],ls[y],l,mid);rs[p]=merge(rs[x],rs[y],mid+1,r);
    tr[p]=max(tr[ls[p]],tr[rs[p]]);
    id[p]=tr[ls[p]]>=tr[rs[p]]?id[ls[p]]:id[rs[p]];
    return p;
}
void query(int cnt,int l,int r,int L,int R){
    if(!cnt)return;
    if(l>=L&&r<=R){
        if(tr[cnt]>ans1){
            ans1=tr[cnt];
            ans2=id[cnt];
        }
        return;
    } 
    int mid=(l+r)>>1;
    if(mid>=L)query(ls[cnt],l,mid,L,R);
    if(mid<R)query(rs[cnt],mid+1,r,L,R);
}
inline void ins(int x,int id){
    if(ch[last][x]){
        int p=last,q=ch[last][x];
        if(l[p]+1==l[q])last=q;
        else{
            int nq=++cnt;l[nq]=l[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[nq]));
            fa[nq]=fa[q];fa[q]=nq;
            for(;ch[p][x]==q;p=fa[p])ch[p][x]=nq;
            last=nq;
        }
    }
    else{
        int p=last,np=++cnt;l[np]=l[p]+1;last=np;
        for(;p&&!ch[p][x];p=fa[p])ch[p][x]=np;
        if(!p)fa[np]=1;
        else{
          int q=ch[p][x];
          if(l[p]+1==l[q])fa[np]=q;
          else{
              int nq=++cnt;l[nq]=l[p]+1;
              memcpy(ch[nq],ch[q],sizeof(ch[nq]));
              fa[nq]=fa[q];fa[q]=fa[np]=nq;
              for(;ch[p][x]==q;p=fa[p])ch[p][x]=nq;
          }    
        }
    }
    upd(T[last],1,n,id);
}
struct edge{int n,to;}e[N];
inline void add(int u,int v){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;}
void dfs(int u){
    for(int i=1;(1<<i)<=deep[u];++i)p[i][u]=p[i-1][p[i-1][u]];
    for(int i=head[u];i;i=e[i].n){
        int v=e[i].to;deep[v]=deep[u]+1;p[0][v]=u;
        dfs(v);T[u]=merge(T[u],T[v],1,n);
    }
}
int main(){
    scanf("%s",s+1);
    n=rd();cnt=1;
    for(int i=1;i<=n;++i){
        last=1;
        scanf("%s",s1);int len=strlen(s1);
        for(int j=0;j<len;++j)ins(s1[j]-'a',i);
    }
    for(int i=1;i<=cnt;++i)if(fa[i])add(fa[i],i);
    dfs(1);int len=strlen(s+1),now=1,le=0;
    for(int i=1;i<=len;++i){
        while(now&&!ch[now][s[i]-'a'])now=fa[now],le=l[now];
        if(!now)now=1;
        if(ch[now][s[i]-'a'])now=ch[now][s[i]-'a'],le++;
        tag[i]=now;mat[i]=le;
    }
    int q=rd(),l1,r1,l2,r2;
    while(q--){
        l2=rd();r2=rd();l1=rd();r1=rd();
        int x=tag[r1];
        if(!x||mat[r1]<r1-l1+1){
            printf("%d 0\n",l2);continue;
        }
        for(int i=20;i>=0;--i)if(l[p[i][x]]>=r1-l1+1)x=p[i][x];    
    
        ans1=0;ans2=l2;;
        query(T[x],1,n,l2,r2);
        printf("%d %d\n",ans2,ans1);
    }
    return 0;
}

转载于:https://www.cnblogs.com/ZH-comld/p/10422490.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值