首页 > 其他 > 详细

[NOI2018]你的名字(后缀自动机+线段树合并)

时间:2020-03-04 23:04:03      阅读:70      评论:0      收藏:0      [点我收藏+]

[NOI2018]你的名字(后缀自动机+线段树合并)

题面

给出一个字符串\(S\),有\(q\)组询问,每次询问给出一个字符串\(T\)和整数\(l,r\).问能从\(T\)中选出多少个本质不同的子串,满足这个子串在\(S\)的区间\([l,r]\)没有出现过。

\(|S| \leq 5 \times 10^5,q \leq 10^5,\sum|T| \leq 5 \times 10^5\)

分析

AC这道题的一瞬间,我感觉和后缀自动姬合二为一,达到了生命的大和谐。

简化问题

考虑一个简化的问题,只考虑\(T\)和整个\(S\)串的答案。
\(S\)\(T\)都建出后缀自动机,记为\(M_1\)\(M_2\).然后把\(T\)串放到\(M_1\)上匹配,记录当前节点\(x_1\)和匹配长度\(matlen\),然后还要记录在\(M_2\)上走到的节点\(x_2\). 对于节点\(x_2\),它贡献的本质不同的串的长度应该在\([len(link(x_2))+1,len(x_2)]\),个数就是区间长度. 但是还要考虑到如果不能与\(S\)匹配,所以如果\(matlen\)落在\([len(link(x_2))+1,len(x_2)]\),贡献的本质的长度范围应该是\([matlen+1,len(x_2)]\).

因此我们对\(M_2\)的每个节点维护一个值\(mlen\)表示该节点与\(S\)的最大匹配长度。每次用matlen去更新答案。更新方法是沿着link树往上跳,设跳到的节点为\(y\),如果\(matlen<len(link(y)+1)\),那么mlen不变,增加的子串个数为\(mlen-len(link(y))\).否则更新\(mlen\)\(matlen\),增加的子串个数为\(matlen-len(link(y))\).

正解

如何求区间\(S[l,r]\)内的字符与\(T\)匹配的结果呢?我们可以用到\(right\)集合。如果\(M_1\)\(right\)集合中存在落在当前需要匹配的区间\([l+matlen,r]\)中,那么匹配长度\(matlen\)就可以+1. 否则也是沿着\(link\)往上跳即可。\(right\)集合可以用权值线段树维护,预处理的时候用线段树合并,查询的时候就是一个区间求和。

细节比较多,见代码注释

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define maxc 26
#define maxn 1000000
#define maxlogn 30
using namespace std;
int n,q;
char s[maxn+5],t[maxn+5];

struct segment_tree{
#define lson(x) (tree[x].ls)
#define rson(x) (tree[x].rs)
    struct node{
        int ls;
        int rs;
        int val;
    }tree[maxn*maxlogn+5];
    int ptr=0;
    void push_up(int x){
        tree[x].val=tree[lson(x)].val+tree[rson(x)].val;
    }
    void update(int &x,int upos,int l,int r){
        x=++ptr;
        if(l==r){
            tree[x].val++;
            return;
        }
        int mid=(l+r)>>1;
        if(upos<=mid) update(lson(x),upos,l,mid);
        else update(rson(x),upos,mid+1,r);
        push_up(x);
    }
    int query(int x,int L,int R,int l,int r){
        if(x==0||L>R) return 0;
        if(L<=l&&R>=r) return tree[x].val;
        int mid=(l+r)>>1;
        int ans=0;
        if(L<=mid) ans+=query(lson(x),L,R,l,mid);
        if(R>mid) ans+=query(rson(x),L,R,mid+1,r);
        return ans; 
    }
    int merge(int x,int y){
        if(!x||!y) return x+y;  
        int now=++ptr;//这样合并不会影响原来的两棵树,类似可持久化
        tree[now].val=tree[x].val+tree[y].val;
        lson(now)=merge(lson(x),lson(y));
        rson(now)=merge(rson(x),rson(y));
        return now; 
    } 
#undef lson
#undef rson
}Tr;

struct SAM{
#define link(x) t[x].link 
#define len(x) t[x].len
    struct node{
        int ch[maxc];
        int link;
        int len;
        int mlen;//该位置代表的后缀向前最长的合法长度(没有和S匹配) 
        int root;//代表的right集合对应的线段树的根节点 
        void clear(){
            //因为要多次重建自动机,做好初始化 
            for(int i=0;i<maxc;i++) ch[i]=0;
            link=len=root=0;
        }
    }t[maxn*2+5];
    const int root=1;
    int last=root;
    int ptr=1;
    inline int size(){
        return ptr;
    }
    void ini(){
        last=root;
        ptr=1;
        t[root].clear();
    }
    int New(){
        ptr++;
        t[ptr].clear();
        return ptr;
    }
    inline int delta(int x,int c){
        return t[x].ch[c];//转移函数 
    }
    void extend(int c,int pos){
        int p=last,cur=New();
        len(cur)=len(p)+1;
        if(pos) Tr.update(t[cur].root,pos,1,n);//因为并不需要T的right集合,插入T的时候把pos设为0 
        while(p&&t[p].ch[c]==0){
            t[p].ch[c]=cur;
            p=link(p);
        }
        if(p==0) link(cur)=root;
        else{
            int q=t[p].ch[c];
            if(len(p)+1==len(q)) link(cur)=q;
            else{
                int clo=New();
                len(clo)=len(p)+1;
                for(int i=0;i<maxc;i++) t[clo].ch[i]=t[q].ch[i];
                link(clo)=link(q);
                link(q)=link(cur)=clo;
                while(p&&t[p].ch[c]==q){
                    t[p].ch[c]=clo;
                    p=link(p);
                }
            }
        } 
        last=cur;
    }
    void topo_sort(){
        static int in[maxn+5];
        queue<int>q;
        for(int i=1;i<=ptr;i++) in[link(i)]++;
        for(int i=1;i<=ptr;i++) if(!in[i]) q.push(i);
        while(!q.empty()){
            int x=q.front();
            q.pop();
            if(link(x)==0) continue;
            in[link(x)]--;
            t[link(x)].root=Tr.merge(t[link(x)].root,t[x].root);
            if(in[link(x)]==0) q.push(link(x));
        }
    }  
}S1,S2;
long long calc(char *t,int l,int r){
    int lent=strlen(t+1);
    S2.ini();
    for(int i=1;i<=lent;i++) S2.extend(t[i]-'a',0);
    for(int i=1;i<=S2.size();i++) S2.t[i].mlen=S2.t[i].len;
    int x1=S1.root,x2=S2.root;
    int matlen=0;//S[l,r]与T的匹配长度 
    long long ans=0;
    for(int i=1;i<=lent;i++){
        int c=t[i]-'a';
        x2=S2.delta(x2,c);
        if(S1.delta(x1,c)&&Tr.query(S1.t[S1.delta(x1,c)].root,l+matlen,r,1,n)){
            //如果S串的ch[x][c]对应的right集合中,有被当前需要匹配的范围[l+tot,r]包含的,就说明当前的T与S[l,r]匹配长度+1. 
            x1=S1.t[x1].ch[c];
            matlen++;
        }else{
            while(x1&&!(S1.delta(x1,c)&&Tr.query(S1.t[S1.delta(x1,c)].root,l+matlen,r,1,n))){//不能匹配,跳link 
                if(matlen==0){
                    x1=0;
                    break;
                }
                matlen--;//减少匹配长度,尝试卡进区间内
                if(matlen==S1.len(S1.link(x1))) x1=S1.link(x1);
            }
            if(!x1){
                x1=S1.root;
                matlen=0;
            }else{
                x1=S1.delta(x1,c);
                matlen++;
            }
        }
        for(int y=x2;y;y=S2.link(y)){
            if(matlen>S2.len(S2.link(y))){//匹配位置在当前节点代表的串[len(link(x))+1,len(x)]内 
                ans+=S2.t[y].mlen-matlen;
                S2.t[y].mlen=matlen;
                break;//此时对祖先节点已经没有影响,可以break 
            }else{//否则这整个区间都是合法的 
                ans+=S2.t[y].mlen-S2.len(S2.link(y));
                S2.t[y].mlen=S2.len(S2.link(y));//这一部分的贡献已经计算过了,要移到前面 
            } 
        }
//      printf("i=%d x1=%d x2=%d,ans=%lld\n",i,x1,x2,ans);
    }
//  printf("----\n");
    return ans;
}


int main(){
#ifndef LOCAL
    freopen("name.in","r",stdin);
    freopen("name.out","w",stdout);
#endif
    int l,r;
    scanf("%s",s+1);
    n=strlen(s+1);
    S1.ini();
    for(int i=1;i<=n;i++) S1.extend(s[i]-'a',i);
    S1.topo_sort();
    scanf("%d",&q);
    for(int i=1;i<=q;i++){
        scanf("%s",t+1);
        scanf("%d %d",&l,&r);
        printf("%lld\n",calc(t,l,r));
    }
}
/*
abc
1
cad 2 3
*/

[NOI2018]你的名字(后缀自动机+线段树合并)

原文:https://www.cnblogs.com/birchtree/p/12416613.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!