bzoj3926 [Zjoi2015]诸神眷顾的幻想乡(广义后缀自动机)

bzoj3926 [Zjoi2015]诸神眷顾的幻想乡

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=3926

题意:
给定一棵n个节点的树,每个节点有颜色c[i],求这棵树上有多少种不同的子串。叶子结点的数量不超过20。

数据范围
1<=n<=100000, 1<=c<=10

题解:
给的就是一棵trie。
因为叶子结点数不超过20,可以依次把每个叶节点提成根,加入跟到叶子的所有串,这样就把所有子串都加入SAM了。
其实广义后缀自动机更像trie,每个点的last是他在trie树上的fa。每次从last添加即可。
注意insert唯一不同的一点是已有了这个转移且right集合不会分裂的话就可以不新开点直接回去了。


统计SAM上本质不同的串个数:
每个状态v对应多个字符串,最长为len(v),最短为minlen(v),
因为minlen(v)=len(pa(v))+1
于是right相同的串个数=len(v)-minlen(v)+1=len(v)-len(pa(v))
于是串个数就是 len(v)len(pa(v))


代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int N=100005;
int n,col[N],head[N],to[2*N],nxt[2*N],C,num=0,du[N],root=1,tail=1;
struct node
{
    int ch[10],len,pa;
}tr[N*40];
void build(int u,int v)
{
    num++;
    to[num]=v;
    nxt[num]=head[u];
    head[u]=num;
}
int add(int last,int c)
{
    if(tr[last].ch[c]&&tr[tr[last].ch[c]].len==tr[last].len+1) return tr[last].ch[c];
    int nd=++tail; tr[nd].len=tr[last].len+1;
    int tmp=last;
    for(;tmp&&!tr[tmp].ch[c];tmp=tr[tmp].pa) tr[tmp].ch[c]=nd;
    if(!tmp) tr[nd].pa=root;
    else
    {
        int B=tr[tmp].ch[c];
        if(tr[B].len==tr[tmp].len+1) tr[nd].pa=B;
        else
        {
            int nB=++tail; tr[nB]=tr[B]; tr[nB].len=tr[tmp].len+1;
            for(;tmp&&tr[tmp].ch[c]==B;tmp=tr[tmp].pa) tr[tmp].ch[c]=nB;
            tr[B].pa=tr[nd].pa=nB;
        }
    }
    return nd;
}
void dfs(int fa,int u,int pos)
{
    int nd=add(pos,col[u]);
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa) continue;
        dfs(u,v,nd);
    }
}
void getans()
{
    LL ret=0;
    for(int i=1;i<=tail;i++) ret+=1LL*(tr[i].len-tr[tr[i].pa].len);
    printf("%lld\n",ret);
}
int main()
{
    scanf("%d%d",&n,&C);
    for(int i=1;i<=n;i++) scanf("%d",&col[i]);
    for(int i=1;i<n;i++)
    {
        int u,v;scanf("%d%d",&u,&v);
        build(u,v); build(v,u);
        du[u]++; du[v]++;
    }
    for(int i=1;i<=n;i++)
    if(du[i]==1) dfs(0,i,root);
    getans();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值