后缀自动机+LCT 【bzoj4545】 DQS的Trie

7 篇文章 0 订阅
4 篇文章 0 订阅

题目大意:
维护一个树,树的边上有一个字母,支持三个操作:
1、查询本质不同的子串的个数;
2、在树上的某一个节点长出一个新的子树;
3、查询某个串出现的次数。

题目分析:
对这棵树维护一个广义的后缀自动机。
对于1操作,只要维护每个节点的max_ len减去parent树上父节点的max_len,详见bzoj4516生成魔咒的题解;
对于3操作,则维护LCT进行链加即可,详见bzoj2555 SubString的题解。
所以说这题是4516和2555二合一啊O。O

注意:1操作要开long long
代码如下:

#include <cstdio>
#include <cstring>
#define N 1200000
using namespace std;
int id,n,m,opt,x,y,rt,sz;
long long ans;
char s[N],q[N<<1];
int fir[N],nes[N<<1],v[N<<1],fa[N],tot=1,last_tot=1;
void edge(int x,int y,char c)
{
    v[++tot]=y;
    q[tot]=c;
    nes[tot]=fir[x];
    fir[x]=tot;
    return;
}
#define edge(x,y,c) edge(x,y,c),edge(y,x,c)
struct splay{
    splay *ch[2],*fa;
    int sum,mark;
    splay();
    void add_mark(int);
    void push_down();
    void push_up();
    int dir();
}*null=new splay;
splay :: splay()
{
    sum=mark=0;
    ch[1]=ch[0]=fa=null;
}
void splay :: add_mark(int v)
{
    if(this==null) return;
    sum+=v;
    mark+=v;
}
void splay :: push_down()
{
    if(mark)
    {
        ch[0]->add_mark(mark);
        ch[1]->add_mark(mark);
        mark=0;
    }
}
void splay ::  push_up()
{
    if(~dir()) fa->push_up();
    push_down();
}
int splay :: dir()
{
    return fa->ch[0]==this?0:fa->ch[1]==this?1:-1;
}
void turn(splay *c,int d)
{
    splay *y=c->ch[d^1];
    c->ch[d^1]=y->ch[d];
    if(y->ch[d]!=null) y->ch[d]->fa=c;
    y->ch[d]=c;
    y->fa=c->fa;
    int k;
    if(~(k=c->dir())) c->fa->ch[k]=y;
    c->fa=y;
}
void splaying(splay *c)
{
    c->push_up();
    int d;
    while(~(d=c->dir()))
    {
        if(d==c->fa->dir()) turn(c->fa->fa,d^1);
        turn(c->fa,d^1);
    }
}
void Access(splay *c)
{
    splay *y=null;
    while(c!=null)
    {
        splaying(c);
        c->ch[1]=y;
        y=c;
        c=c->fa;
    }
    return;
}
void Cut(splay *x)
{
    Access(x),splaying(x);
    x->ch[0]->fa=null;
    x->ch[0]=null;
}
void Link(splay *x,splay *y)
{
    Cut(x);
    x->fa=y;
}
struct SAM{
    SAM *son[26],*fa;
    int max_len;
    splay *tree;
    SAM(int _):max_len(_)
    {
        memset(son,0,sizeof(son));
        fa=NULL;
        tree=new splay;
    }
}*last[N],*root=new SAM(0);
SAM* extend(SAM *last,int x)
{
    SAM *p=last;
    SAM *np=new SAM(p->max_len+1);
    while(p && !p->son[x]) p->son[x]=np,p=p->fa;
    if(!p) np->fa=root;
    else
    {
        SAM *q=p->son[x];
        if(q->max_len==p->max_len+1) np->fa=q;
        else
        {
            SAM *nq=new SAM(p->max_len+1);
            ans-=q->max_len-q->fa->max_len;
            q->tree->push_up();
            nq->tree->sum=q->tree->sum;
            Link(q->tree,nq->tree);
            Link(nq->tree,q->fa->tree);
            nq->fa=q->fa;
            np->fa=nq; q->fa=nq;
            ans+=q->max_len-q->fa->max_len;
            ans+=nq->max_len-nq->fa->max_len;
            memcpy(nq->son,q->son,sizeof(nq->son));
            for(;p && p->son[x]==q;p=p->fa) p->son[x]=nq;
        }
    }
    ans+=np->max_len-np->fa->max_len;
    Link(np->tree,np->fa->tree);
    Access(np->tree),splaying(np->tree);
    np->tree->add_mark(1);
    return np;
}
void dfs(int c)
{
    for(int t=fir[c];t;t=nes[t])
    {
        if(v[t]==fa[c]) continue;
        if(t<=last_tot) break;
        last[v[t]]=extend(last[c],q[t]-'a');
        fa[v[t]]=c;
        dfs(v[t]);
    }
    return;
}
int query(char *s)
{
    SAM *c=root;
    for(int i=0;s[i];i++)
    {
        if(!c->son[s[i]-'a']) return 0;
        c=c->son[s[i]-'a'];
    }
    c->tree->push_up();
    return c->tree->sum;
}
int main()
{
    scanf("%d",&id);
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%s",&x,&y,s);
        edge(x,y,s[0]);
    }
    last[1]=root;
    dfs(1);
    scanf("%d",&m);
    while(m--)
    {
        scanf("%d",&opt);
        switch(opt)
        {
        case 1:
            printf("%lld\n",ans);
            break;
        case 2:
            scanf("%d%d",&rt,&sz);
            last_tot=tot;
            for(int i=1;i<sz;i++)
            {
                scanf("%d%d%s",&x,&y,s);
                edge(x,y,s[0]);
            }
            dfs(rt);
            break;
        case 3:
            scanf("%s",s);
            printf("%d\n",query(s));
            break;
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值