[BZOJ2555]SubString(后缀自动机+LCT)

=== ===

这里放传送门

=== ===

题解

这题如果没有什么奇怪的修改操作的话就是在后缀自动机上匹配给定串然后求那个节点Right集合的大小就可以了,但平常递推Right集合大小的时候是要求后缀自动机构建完成以后按照拓扑序来搞的,这次不能每次插入一段后缀以后都重新构建Right集合,就要考虑动态维护这个东西。每次加入一个新节点的时候它会顺着它的fa指针更新所有路径上的Right集合大小,这样的话如果把Parent树建出来,每次就是新增节点还有更改树链还有查询点权的操作。因为在建立后缀自动机的过程中很多已经连好的节点的fa指针都会被更改,所以应该用支持动态断边连边的LCT来维护这个东西。在插入nq节点的时候因为要更改q节点的fa,网上很多代码都是直接在树链上减去q节点的权值,实际上观察它的操作过程,就是在q和它原来的fa之间插入了一个nq节点,而nq节点本身是没有权值的,所以直接把q的权值赋给nq就不需要做别的更改。注意访问q节点的权值的时候必须先Splay一下保证它的标记已经被下放,这样才能得到正确的权值。然后每次insert一个字符结束的时候把np节点到Root的那一条链全都+1就可以了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,len,ch[1200100][30],fa[1200100],step[1200100];
int Root,cnt,last,p,q,np,nq,top,mask,lastans;
char s[3000010];
struct Node{
    Node *ch[2],*fa;
    int val,dlt;
    bool rev;
    Node();
    void Add(int v);
    void Rev();
    void push();
    bool pl(){return this==fa->ch[1];}
    bool is_root(){return this!=fa->ch[1]&&this!=fa->ch[0];}
}*null,P[1200100],*ptr[1200100],*st[1200100];
Node::Node(){ch[0]=ch[1]=fa=null;val=dlt=rev=0;}
void Node::Add(int v){val+=v;dlt+=v;}
void Node::Rev(){swap(ch[0],ch[1]);rev^=1;}
void Node::push(){
    if (rev==true){
        if (ch[0]!=null) ch[0]->Rev();
        if (ch[1]!=null) ch[1]->Rev();
        rev=false;
    }
    if (dlt!=0){
        if (ch[0]!=null) ch[0]->Add(dlt);
        if (ch[1]!=null) ch[1]->Add(dlt);
        dlt=0;
    }
}
int New(){P[++cnt]=Node();ptr[cnt]=P+cnt;return cnt;}
void decodeWithMask(int mask){
    for (int i=0;i<len;i++){
        mask=(mask*131+i)%len;
        swap(s[i],s[mask]);
    }
}
void Rotate(Node *k){
    Node *r=k->fa;
    if (r==null||k==null) return;
    int x=k->pl()^1;
    r->ch[x^1]=k->ch[x];
    if (k->ch[x]!=null)
      r->ch[x^1]->fa=r;
    if (!r->is_root())
      r->fa->ch[r->pl()]=k;
    k->fa=r->fa;
    r->fa=k;
    k->ch[x]=r;
}
void Splay(Node *r){
    Node *tmp=r;
    top=0;st[++top]=r;
    while (!tmp->is_root()){
        st[++top]=tmp->fa;tmp=tmp->fa;
    }
    for (int i=top;i>=1;i--) st[i]->push();
    for (;!r->is_root();Rotate(r))
      if (!r->fa->is_root())
        Rotate(r->pl()==r->fa->pl()?r->fa:r);
}
void Access(Node *k){
    Node *r=null;
    while (k!=null){
        Splay(k);
        k->ch[1]=r;
        r=k;k=k->fa;
    }
}
void Change_root(Node *x){Access(x);Splay(x);x->Rev();}
void Link(Node *x,Node *y){Change_root(x);x->fa=y;}
void Cut(Node *x,Node *y){
    Change_root(y);
    Access(x);Splay(x);
    x->ch[0]=y->fa=null;
}
void Get_path(Node *x,Node *y){
    Change_root(y);
    Access(x);Splay(x);
}
void ChgFa(int u,int f){
    if (fa[u]!=0) Cut(ptr[u],ptr[fa[u]]);
    fa[u]=f;Link(ptr[u],ptr[f]);
}
void Addnum(int u){
    Get_path(ptr[u],ptr[Root]);
    ptr[u]->Add(1);
}
void insert(int c){
    p=last;last=np=New();
    step[np]=step[p]+1;
    while (ch[p][c]==0&&p!=0){
        ch[p][c]=np;p=fa[p];
    }
    if (p==0){ChgFa(np,Root);Addnum(np);return;}
    q=ch[p][c];
    if (step[q]==step[p]+1){ChgFa(np,q);Addnum(np);return;}
    nq=New();step[nq]=step[p]+1;
    Splay(ptr[q]);ptr[nq]->val=ptr[q]->val;//访问节点权值之前要先Splay
    memcpy(ch[nq],ch[q],sizeof(ch[q]));
    ChgFa(nq,fa[q]);ChgFa(q,nq);ChgFa(np,nq);
    while (ch[p][c]==q){ch[p][c]=nq;p=fa[p];}
    Addnum(np);
}
int Query(){
    int now=Root;
    for (int i=0;i<len;i++)
      now=ch[now][s[i]-'A'];
    if (now==0) return 0;
    Splay(ptr[now]);
    return ptr[now]->val;
}
int main()
{
    null=new Node;*null=Node();
    scanf("%d\n",&n);
    scanf("%s",s);len=strlen(s);
    Root=last=New();
    for (int i=0;i<len;i++) insert(s[i]-'A');
    for (int i=1;i<=n;i++){
        char opt[7];
        scanf("%s",opt);
        scanf("%s",s);len=strlen(s);
        decodeWithMask(mask);
        if (opt[0]=='A')
          for (int j=0;j<len;j++)
            insert(s[j]-'A');
        else{
            lastans=Query();
            printf("%d\n",lastans);
            mask^=lastans;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值