CF666E Forensic Examination

https://www.luogu.org/problemnew/show/CF666E

本质还是一个串在一些串里的匹配

考虑对模板串建广义SAM

既然有S的一些匹配关系的询问,套路地,

把S在上面跑,

pi[i]记录S的[1~i]的前缀在SAM上的匹配长度

pos[i]记录S的[1~i]的前缀在SAM的匹配最终位置

询问的时候,如果匹配长度不够l,那么输出(L,0)

从pos[i]往上倍增,直到len刚好大于pr-pl+1(开始的时候,如果匹配长度不够,直接返回0)

整个子树对应的叶子就是所有出现位置。

没有的话,还要检查,输出(L,0)

 

至于在[l,r]哪个串出现位置最多,线段树合并即可。

或者dsu on the tree(即区间众数) 拿两个桶维护一下。

一个维护每个值出现次数,以及出现次数为某个次数的值有多少种。

 

 

代码细节:

1.线段树合并:由于之前的还要用,所以合并的时候务必新建一个节点,空间变成2倍,但是本质上参与合并的点数还是nlogn的。

否则类似主席树,之前的值会改变,无法查询!

2.记录匹配长度是在S的位置记录!不是一般的在SAM上记录最长匹配位置!(这个简直大错特错,因为可能和原子串根本不符合匹配!长度大于,但是可能是S别的地方匹配过来的!)

3.pi数组的长度是N,不是M(N,M)并不同阶。

代码:

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define mid ((l+r)>>1)
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=5e5+5;
const int M=1e5+5;
int n,m;
char s[N],a[M];
pair<int,int> cmp(pair<int,int>a,pair<int,int>b){
    if(a.second==b.second) {
        return a.first<b.first?a:b;
    }    
    return a.second>b.second?a:b;
}
struct SAM{
    int f[2*M][20],ch[2*M][26],len[2*M];
    int nd,cnt;
    void init(){
        nd=cnt=1;
    }
    struct node{
        int ls,rs;
        int mx,id;
    }t[4*M*20];
    int rt[2*M];
    int pos[N];
    int pi[N];
    int num;
    void pushup(int x){
        if(t[t[x].ls].mx>t[t[x].rs].mx||(t[t[x].ls].mx==t[t[x].rs].mx&&t[t[x].ls].id<t[t[x].rs].id)){
            t[x].mx=t[t[x].ls].mx;
            t[x].id=t[t[x].ls].id;
        }
        else{
            t[x].mx=t[t[x].rs].mx;
            t[x].id=t[t[x].rs].id;
        }
    }
    void upda(int &x,int l,int r,int to,int c){
        if(!x) x=++num;
        if(l==r){
            t[x].mx+=c;t[x].id=l;return;
        }
        if(to<=mid) upda(t[x].ls,l,mid,to,c);
        else upda(t[x].rs,mid+1,r,to,c);
        pushup(x);
    }
    int merge(int x,int y,int l,int r){
        if(!x||!y) return x+y;
        int ret=++num;
        if(l==r){
            t[ret].mx=t[x].mx+t[y].mx;
            t[ret].id=t[x].id;
            return ret;
        }
        t[ret].ls=merge(t[x].ls,t[y].ls,l,mid);
        t[ret].rs=merge(t[x].rs,t[y].rs,mid+1,r);
        pushup(ret);
        return ret;
    }
    pair<int,int> query(int x,int l,int r,int L,int R){
        //cout<<" quering "<<l<<" "<<r<<" "<<L<<" "<<R<<" :: "<<t[x].id<<" "<<t[x].mx<<endl;
        if(L<=l&&r<=R){
            return make_pair(t[x].id,t[x].mx);
        }
        pair<int,int>ret;
        ret.first=0,ret.second=-2333;
        if(L<=mid) ret=cmp(ret,query(t[x].ls,l,mid,L,R));
        if(mid<R) ret=cmp(ret,query(t[x].rs,mid+1,r,L,R));
        return ret;
    }
    void ins(int l,int c,int d){
        int p=nd;nd=++cnt;len[nd]=l;
        for(;p&&ch[p][c]==0;p=f[p][0]) ch[p][c]=cnt;
        int q;
        if(!p){
            f[nd][0]=1;goto abc;
        }
        q=ch[p][c];
        if(len[q]==len[p]+1){
            f[nd][0]=q;goto abc;
        }
        len[++cnt]=len[p]+1;
        f[cnt][0]=f[q][0];f[q][0]=f[nd][0]=cnt;
        for(reg i=0;i<26;++i) ch[cnt][i]=ch[q][i];
        for(;p&&ch[p][c]==q;p=f[p][0]) ch[p][c]=cnt;
        abc:;
        upda(rt[nd],1,m,d,1);
    }
    void wrk(char *s){
        int lth=strlen(s+1);
        int now=1;
        int l=0;
        for(reg i=1;i<=lth;++i){
            int x=s[i]-'a';
        //    cout<<" wrk "<<i<<" "<<now<<" "<<l<<endl;
            if(ch[now][x]==0){
                while(now&&ch[now][x]==0) now=f[now][0];
                if(!now){
                    l=0;
                    pos[i]=1;
                    now=1;pi[i]=0;
                }
                else{
                    l=len[now]+1;
                    now=ch[now][x];
                    pi[i]=l;pos[i]=now;
                }
            }else{
        //        cout<<" ok "<<endl;
                ++l;
                now=ch[now][x];
                pi[i]=l;pos[i]=now;
            }
        }
    }
    struct edge{
        int nxt,to;
    }e[2*M];
    int hd[2*M],tot;
    void add(int x,int y){
        e[++tot].nxt=hd[x];
        e[tot].to=y;
        hd[x]=tot;
    }
    void dfs(int x){
        //cout<<" xx "<<x<<" fa "<<f[x][0]<<endl;
        //cout<<" rt[21] "<<rt[21]<<" "<<t[rt[21]].id<<" "<<t[rt[21]].mx<<endl;
        for(reg i=hd[x];i;i=e[i].nxt){
            int y=e[i].to;
            dfs(y);
            //cout<<" mergeing "<<rt[x]<<" "<<rt[y]<<endl;
        //    cout<<" before  rt[21] "<<rt[21]<<" "<<t[rt[21]].id<<" "<<t[rt[21]].mx<<endl;
            rt[x]=merge(rt[x],rt[y],1,m);
            //cout<<" after rt[21] "<<rt[21]<<" "<<t[rt[21]].id<<" "<<t[rt[21]].mx<<endl;
        }
    }
    void build(){//add_edge+dfs+merge+beizeng
        for(reg i=2;i<=cnt;++i) add(f[i][0],i);
        dfs(1);
        for(reg j=1;j<=19;++j){
            for(reg i=1;i<=cnt;++i){
                f[i][j]=f[f[i][j-1]][j-1];
            }
        }
    }
    pair<int,int>sol(int p,int l,int L,int R,int lp){
        //if(lp==53) cout<<" p "<<p<<" ll "<<l<<" : "<<pos[p]<<" "<<pi[pos[p]]<<" L R "<<L<<" "<<R<<" lp "<<lp<<endl;
        if(pi[p]<l) return make_pair(L,0);
        p=pos[p];
        for(reg j=19;j>=0;--j){
            if(len[f[p][j]]>=l) p=f[p][j];
        }
        //if(lp==53) cout<<" after jump "<<p<<" "<<len[p]<<" : "<<f[p][0]<<" "<<len[f[p][0]]<<" "<<rt[p]<<endl;
        pair<int,int>ret=query(rt[p],1,m,L,R);
        if(ret.first==0) ret.first=L;
        return ret;
    }
    void main(){
        scanf("%s",s+1);
        //num=1;
        //rt[1]=1;
        
        rd(m);
        init();
        for(reg i=1;i<=m;++i){
            int now=0;
            scanf("%s",a+1);
            now=strlen(a+1);
            nd=1;
            for(reg j=1;j<=now;++j){
                ins(j,a[j]-'a',i);
            }
            //cout<<" rt[2] "<<rt[2]<<" "<<t[rt[2]].id<<" "<<t[rt[2]].mx<<endl;
        }
        wrk(s);
        //int lll=strlen(s+1);
//        for(reg i=1;i<=lll;++i){
//            cout<<i<<" : "<<pos[i]<<endl;
//        }
        build();
        //cout<<" rt[2] "<<rt[2]<<" "<<t[rt[2]].id<<" "<<t[rt[2]].mx<<endl;
        
        int q;
        rd(q);
        int l,r,pl,pr;
        int o=0;
        while(q--){
            rd(l);rd(r);rd(pl);rd(pr);
            pair<int,int>ans=sol(pr,pr-pl+1,l,r,++o);
            printf("%d %d\n",ans.first,ans.second);
        }
    }
}sam;
int main(){
    sam.main();
    return 0;
}
}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/31 10:57:37
*/

总结:
思路还是简单自然的

要多串的子串匹配,就广义SAM

询问和S子串有关,就先跑一下匹配

然后询问,考虑结尾位置,然后倍增,子树出现标记?线段树合并!

细节还是要注意。

 

posted @ 2018-12-31 10:09  *Miracle*  阅读(569)  评论(0编辑  收藏  举报