BJ模拟:String(SAM+LCT+主席树)

本文探讨了一种高效的字符串处理算法,通过动态插入字符并利用数据结构如后缀自动机和链式切割树(LCT),实现了对字符串的高效查询与更新。特别地,文章介绍了一种离线算法,用于解决在给定区间内寻找出现两次的最长子串问题。

传送门

题意:
给字符串 S S ,支持:
1.末尾加入字符。
2.查询[l,r]中出现两次的最长字符串。

题解:
好题。

考虑离线做法:
动态插入后面的字符,更新前面 l l 的答案。

当我们插入一个字符r的时候,与前面的公共子串为它的后缀。 我们考虑暴力跳 fail f a i l 链来更新答案:
每个位置记last表示endpos的最后一个位置,那么这个位置能更新 ilen[i]+1 i − l e n [ i ] + 1 往左的 l l ,以及ilen[i]+1~i的l。 其中第一部分的答案为 len[i] l e n [ i ] ,第二部分则为 len[i](p(ilen[i]+1)) l e n [ i ] − ( p − ( i − l e n [ i ] + 1 ) ) 。 我们分别在线段树上维护两个 tag t a g 即可。

发现这个覆盖过程就是 lct l c t access a c c e s s ,那么我们用lct来维护这个last即可。(均摊 O(nlogn) O ( n log ⁡ n ) ,想不到字符串有这么优美的性质)

同样,在线的话把线段树可持久化就行了。

#include <bits/stdc++.h>
#include <tr1/unordered_map>
using namespace std;
typedef long long LL;
typedef pair <int,int> pii;
typedef unsigned long long ULL;

const int RLEN=1<<18|1;
inline char nc() {
    static char ibuf[RLEN],*ib,*ob;
    (ib==ob) && (ob=(ib=ibuf)+fread(ibuf,1,RLEN,stdin));
    return (ib==ob) ? -1 : *ib++;
}
inline int rd() {
    char ch=nc(); int i=0,f=1;
    while(!isdigit(ch)) {if(ch=='-')f=-1; ch=nc();}
    while(isdigit(ch)) {i=(i<<1)+(i<<3)+ch-'0'; ch=nc();}
    return i*f;
}
inline void W(int x) {
    static int buf[50];
    if(!x) {putchar('0'); return;}
    if(x<0) {putchar('-'); x=-x;}
    while(x) {buf[++buf[0]]=x%10; x/=10;}
    while(buf[0]) {putchar(buf[buf[0]--]+'0');}
}

const int N=2e5+50, INF=0x3f3f3f3f, lg=200;
int n;
namespace sgt {
    int lc[N*lg],rc[N*lg],mx[N*lg],mxa[N*lg],rt[N],tot;
    inline void copy(int &x,int y) {
        x=++tot; lc[x]=lc[y]; rc[x]=rc[y]; mx[x]=mx[y]; mxa[x]=mxa[y];
    }
    inline void modify(int &y,int x,int l,int r,int L,int R,int v) {
        if(L>R) return;
        copy(y,x); 
        if(l==L&&r==R) {mx[y]=max(mx[y],v); return;}
        int mid=(l+r)>>1;
        if(R<=mid) modify(lc[y],lc[x],l,mid,L,R,v);
        else if(L>mid) modify(rc[y],rc[x],mid+1,r,L,R,v);
        else modify(lc[y],lc[x],l,mid,L,mid,v), modify(rc[y],rc[x],mid+1,r,mid+1,R,v-(mid+1-L));
    }
    inline void modifya(int &y,int x,int l,int r,int L,int R,int v) {
        copy(y,x); 
        if(L<=l&&r<=R) {mxa[y]=max(mxa[y],v); return;}
        int mid=(l+r)>>1;
        if(R<=mid) modifya(lc[y],lc[x],l,mid,L,R,v);
        else if(L>mid) modifya(rc[y],rc[x],mid+1,r,L,R,v);
        else modifya(lc[y],lc[x],l,mid,L,R,v), modifya(rc[y],rc[x],mid+1,r,L,R,v);
    }
    inline int ask(int x,int l,int r,int p) {
        if(!x) return -INF;
        int rs=max(mx[x]-p+l,mxa[x]);
        if(l==r) return rs;
        int mid=(l+r)>>1;
        if(p<=mid) rs=max(rs, ask(lc[x],l,mid,p));
        else rs=max(rs, ask(rc[x],mid+1,r,p));
        return rs;
    }
    inline int ask(int l,int r) {
        return ask(rt[r],1,n,l);
    }
}
namespace lct {
    struct node {
        node *lc,*rc,*fa;
        int last;
        node() : lc(NULL),rc(NULL),fa(NULL){}
    }Pool[N];
    inline bool isroot(node *x) {return (!x->fa) || (x->fa->lc!=x && x->fa->rc!=x);}
    inline bool which(node *x) {return x->fa->lc==x;}
    inline void rotate(node *x) {
        node *y=x->fa, *z=y->fa;
        if(!isroot(y)) (z->lc==y ? z->lc : z->rc)=x;
        x->fa=z; y->fa=x;
        if(y->lc==x) {
            node *b=x->rc;
            x->rc=y; y->lc=b;
            if(b) b->fa=y;
        } else {
            node *b=x->lc;
            x->lc=y; y->rc=b;
            if(b) b->fa=y;
        }
    }
    inline void splay(int p) {
        static node* stk[N];
        node *x=p+Pool;
        int tl; stk[tl=1]=x;
        for(node *y=x; !isroot(y); y=y->fa) stk[++tl]=y->fa;
        for(int i=tl-1;i;i--) stk[i]->last=stk[i+1]->last;
        while(!isroot(x)) {
            node *y=x->fa;
            if(!isroot(y)) {
                if(which(x)==which(y)) rotate(y);
                else rotate(x);
            } rotate(x);
        }
    }
    inline void link(int p,int q) {
        node *x=Pool+p, *y=Pool+q;
        splay(p); x->fa=y;
    }
    inline void cut(int p,int q) {
        node *x=Pool+p, *y=Pool+q;
        splay(p); x->fa=NULL;   
    }
    inline void access(int &x,int p,int pos,int *len) {
        node *t=(Pool+p);
        for(node *y=NULL; t; y=t, t=t->fa) {
            splay(t-Pool); 
            if(t->last && t->last<pos) {
                sgt::modify(x,x,1,n,t->last-len[t-Pool]+1,t->last,len[t-Pool]);
                sgt::modifya(x,x,1,n,1,t->last-len[t-Pool]+1,len[t-Pool]);
            }
            if(t->rc) t->rc->last=t->last;
            t->rc=y; t->last=pos; 
        }
    }
}
namespace sam {
    int son[N][26],tot=1,l[N],fail[N],lst=1;
    inline void extend(int c,int pos) {
        sgt::rt[pos]=sgt::rt[pos-1];

        int p=++tot; l[p]=l[lst]+1;
        (lct::Pool+p)->last=pos;
        while(lst && !son[lst][c]) son[lst][c]=p, lst=fail[lst];
        if(!lst) fail[p]=1, lct::link(p,1);
        else {
            int q=son[lst][c];
            if(l[q]==l[lst]+1) fail[p]=q,lct::link(p,q);
            else {
                int np=++tot; fail[np]=fail[q]; l[np]=l[lst]+1;
                memcpy(son[np],son[q],sizeof(son[np]));
                fail[np]=fail[q]; fail[q]=fail[p]=np;
                while(lst && son[lst][c]==q) son[lst][c]=np, lst=fail[lst];
                (lct::Pool+np)->last=(lct::splay(q),(lct::Pool+q)->last);

                lct::access(sgt::rt[pos],fail[np],pos,l);
                lct::cut(q,fail[np]); 
                lct::link(np,fail[np]);
                lct::link(q,np);
                lct::link(p,np);
            }
        }

        lct::access(sgt::rt[pos],p,pos,l);
        lst=p;
    }
}
char ch[N]; int len,type,la;
int main() {
    sgt::mx[0]=sgt::mxa[0]=-INF;    
    scanf("%d%s",&type,ch+1);
    len=strlen(ch+1); 
    int m=rd(); n=len+m;
    for(int i=1;i<=len;i++) 
        sam::extend(ch[i]-'a',i);
    for(int i=m;i;i--) {
        int op=rd();
        if(op==1) {
            ch[++len]=(nc()-'a'+la*type)%26+'a';
            sam::extend(ch[len]-'a',len);
        } else {
            int l=(rd()-1+la*type)%len+1, r=(rd()-1+la*type)%len+1;            
            W(la=max(sgt::ask(l,r),0)); putchar('\n');  
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值