BZOJ3926: [Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机

题目大意:N个节点的树,每个节点有一种颜色,问存在多少种颜色序列不同的路径。
1<=n<=100000,颜色种类<=10,叶子节点最多有10个
广义后缀自动机是把多个串建到同一个后缀自动机中,该自动机可以识别所有串的所有后缀。以一棵trie树为例,广义后缀自动机在分叉处保存当前的last指针,并在一条支路结束后将last指针调回分叉处保存的指针,然后再进入另一支路。
广义后缀自动机的构建要注意:新加入一个字符x,可能当前节点已经有能转移向x的指针。此时若那个节点的深度刚好为当前节点+1,则那个节点就是我们本应该新建的节点,则本次不新建节点。如果深度不为当前节点+1,则仍然需要分叉,此时新建节点并正常处理即可。
对于本题,由于叶子节点<=10个,显然任何一条路径都可以看做某两个叶子节点之间路径的一部分。所以从每个叶子节点开始进行一次DFS,得到的广义后缀自动机就能表示所有的路径,又由于parent树上每个节点都是一个本质相同的字符串集合,所以节点个数(不包含根)就是本质不同的串数量。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#define gm 100001
using namespace std;
typedef unsigned long long cnt_t;
int n,c;
struct node
{
    node *s[10],*f;
    size_t dpt;
    bool used;
    node(size_t dpt=0):s(),f(),dpt(dpt),used(0){}
    inline void copy(const node* x){f=x->f,memcpy(s,x->s,sizeof s);}
    inline void* operator new(size_t)
    {
        static node *s,*t;
        if(s==t) s=(node*)malloc(sizeof(node)<<16),t=s+(1<<16);
        return s++;
    }
    cnt_t size()
    {
        cnt_t res=dpt-f->dpt;
        used=1;
        for(int i=0;i<c;++i)
        if(s[i]&&!s[i]->used)
        res+=s[i]->size();
        return res;
    }
};
struct sam
{
    node *rt,*last;
    sam():rt(new node),last(rt){}
    inline void load(){last=rt;}
    inline void push_back(int x)
    {
        node *p=last;
        if(p->s[x]&&p->dpt+1==p->s[x]->dpt){last=p->s[x];return;}
        node *np=last=new node(p->dpt+1);
        while(p&&!p->s[x]) p->s[x]=np,p=p->f;
        if(!p) {np->f=rt;return;}
        node *q=p->s[x];
        if(p->dpt+1==q->dpt) {np->f=q;return;}
        node *nq=new node(p->dpt+1);
        nq->copy(q);
        np->f=nq,q->f=nq;
        while(p&&p->s[x]==q) p->s[x]=nq,p=p->f;
    }
    inline void operator >> (node*& x)
    {
        x=last;
    }
    inline void operator << (node* x)
    {
        last=x;
    }
    inline cnt_t size()
    {
        cnt_t res=0;
        for(int i=0;i<c;++i)
        if(rt->s[i]&&!rt->s[i]->used) res+=rt->s[i]->size();
        return res;
    }
}tr;
int col[gm],rd[gm];
struct e
{
    int t;
    e *n;
    e(int t,e *n):t(t),n(n){}
}*f[gm];
void dfs(int x,int from=0)
{
    node *cache;
    tr.push_back(col[x]);
    tr>>cache;
    for(e *i=f[x];i;i=i->n)
        if(i->t!=from)
        dfs(i->t,x),tr<<cache;
}
int main()
{
    scanf("%d%d",&n,&c);
    for(int i=1;i<=n;++i)
    scanf("%d",col+i);
    for(int i=1,u,v;i<n;++i)
    {
        scanf("%d%d",&u,&v);
        f[u]=new e(v,f[u]);
        f[v]=new e(u,f[v]);
        ++rd[u],++rd[v];
    }
    for(int i=1;i<=n;++i)
    if(rd[i]==1) tr.load(),dfs(i);
    printf("%llu\n",tr.size());
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值