BJ模拟 String【后缀自动机+LCT+可持久化线段树】

题目描述:

给你一个长度为n的字符串S,你需要维护这个字符串S并支持两种操作:
1、在字符串S末尾插入一个字符。
2、记字符串T为字符串S从第 l 个字符到第 r 个字符所构成的子串。询问字符串T中最长的子串使得该子串在T中出现过至少两次(例如:T=”ababa”,最长的子串应为aba,长度为3),并输出它的长度。如果不存在这样的子串,则输出0。
强制在线,n,m<=50000。

解题思路:

不考虑插入的话。。。我们从1到n加入每个字符。。然后在加入第r个字符的时候回答所有形如[l:r]的询问。。以下的当前串均指s[1:r]。。

后缀自动机的parent树,记根到节点x形成的字符串为str[x]

考虑在每个后缀树节点x上维护一个last[x]表示str[x]在当前串中最后一次出现的右端点,也就是right集合中的最大元素。

每当加入一个字符s[r]的时候parent树上会加入一个叶节点u,此时我们应该把根到u的路径上的last都赋值为r。。

再仔细思考一下。。。会发现根据原last(记作last’[x])和r可以更新答案。因为[last’[x]-maxlen[x]+1:last’[x]]和[r-maxlen[x]+1:r]是str[x]的最后两次出现。于是用一个线段树就可以维护答案了,注意还有长度在minlen[x]~maxlen[x]之间的子串也可以更新,所以要记录两个tag,一个记录len,一个记录pos。

链赋值对应着LCT的基本操作access。。于是我们用LCT来维护这个last。注意到同一棵splay里的last是一样的,我们只用在每棵splay的根节点记录last即可,这样修改就是 O(logn) O ( l o g n ) 的了,妙妙。

于是就能维护答案了

在线插入很简单。。因为答案是维护在线段树上的。。。把线段树可持久化就行了,第i棵线段树维护区间[1,i]即可。

#include<bits/stdc++.h>
#define pii pair<int,int>
#define mp make_pair
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();c!='-'&&(c<'0'||c>'9');c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=200005,M=10000005;
int ol,ans,n,m,mxn;
int tot=1,last=1,son[N][26],fa[N],mx[N],r[N];
char s[N];
struct node{int lc,rc,mxl,mxp,tim;}tr[M];
int ttot,rt[N];
int modify(int y,int l,int r,int ql,int qr,int vl,int vp)
{
    int x=tr[y].tim==n?y:++ttot;tr[x]=tr[y],tr[x].tim=n;
    if(ql>qr)return x;
    if(ql<=l&&r<=qr)
    {
        tr[x].mxl=max(tr[x].mxl,vl);
        tr[x].mxp=max(tr[x].mxp,vp);
        return x;
    }
    int mid=l+r>>1;
    if(ql<=mid)tr[x].lc=modify(tr[y].lc,l,mid,ql,qr,vl,vp);
    if(qr>mid)tr[x].rc=modify(tr[y].rc,mid+1,r,ql,qr,vl,vp);
    return x;
}
pii query(int x,int l,int r,int p)
{
    if(!x)return mp(-1,-1);
    if(l==r)return mp(tr[x].mxl,tr[x].mxp);
    int mid=l+r>>1;pii res;
    res=p<=mid?query(tr[x].lc,l,mid,p):query(tr[x].rc,mid+1,r,p);
    return mp(max(res.first,tr[x].mxl),max(res.second,tr[x].mxp));
}
struct LCT
{
    int son[N][2],fa[N];
    inline int which(int x){return son[fa[x]][1]==x;}
    inline bool isrt(int x){return son[fa[x]][0]!=x&&son[fa[x]][1]!=x;}
    inline void rotate(int x)
    {
        int y=fa[x],z=fa[y],t=which(x);
        if(isrt(y))r[x]=r[y],r[y]=0;
        else son[z][which(y)]=x;
        fa[x]=z,fa[y]=x;
        son[y][t]=son[x][t^1],son[x][t^1]=y;
        if(son[y][t])fa[son[y][t]]=y;
    }
    inline void splay(int x)
    {
        while(!isrt(x))
        {
            if(!isrt(fa[x]))rotate(which(x)==which(fa[x])?fa[x]:x);
            rotate(x);
        }
    }
    inline void access(int x)
    {
        for(int y=0;x;y=x,x=fa[x])
        {
            splay(x);
            if(son[x][1])r[son[x][1]]=r[x];
            rt[n]=modify(rt[n],1,mxn,1,r[x]-mx[x],mx[x],-1);
            rt[n]=modify(rt[n],1,mxn,max(1,r[x]-mx[x]+1),r[x],-1,r[x]);
            son[x][1]=y;
            if(!fa[x])r[x]=n;
        }
    }
}lct;
void extend(int c)
{
    int p=last,np=last=++tot;mx[np]=mx[p]+1;
    while(p&&!son[p][c])son[p][c]=np,p=fa[p];
    if(!p)fa[np]=lct.fa[np]=1,lct.access(np);
    else
    {
        int q=son[p][c];
        if(mx[q]==mx[p]+1)fa[np]=lct.fa[np]=q,lct.access(np);
        else
        {
            int nq=++tot;mx[nq]=mx[p]+1;memcpy(son[nq],son[q],sizeof(son[q]));
            fa[nq]=lct.fa[nq]=fa[q],lct.splay(q),r[nq]=r[q],lct.access(nq);
            fa[q]=lct.fa[q]=fa[np]=lct.fa[np]=nq,r[np]=n;
            while(p&&son[p][c]==q)son[p][c]=nq,p=fa[p];
        }
    }
}
int main()
{
    //freopen("lx.in","r",stdin);
    ol=getint();scanf("%s",s+1),m=getint();
    int len=strlen(s+1);mxn=len+m;
    while(n<len)++n,rt[n]=rt[n-1],extend(s[n]-'a');
    while(m--)
    {
        int op=getint(),l,r,c;
        if(op==1)c=(getchar()-'a'+ans*ol)%26,++n,rt[n]=rt[n-1],extend(c);
        else
        {
            int l=(getint()-1+ans*ol)%n+1,r=(getint()-1+ans*ol)%n+1;
            pii res=query(rt[r],1,mxn,l);
            ans=max(res.first,res.second-l+1);
            printf("%d\n",ans);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值