【BZOJ3926】诸神眷顾的幻想乡(ZJOI2015)-广义后缀自动机

测试地址:诸神眷顾的幻想乡
做法:本题需要用到广义后缀自动机。
仔细读题,我们发现树的叶子节点非常少,只有 20 20 个,那么我们把从一个叶子节点到另一个叶子节点所经过的串列出来,我们发现这些串包含树上的每条路径,证明是显然的。
接下来出现了一个问题,后缀自动机是处理单个串的子串的一个数据结构,现在我们有那么多个串,用后缀自动机好像没办法做?所以我们需要用到后缀自动机的扩展——广义后缀自动机。
如果说普通的后缀自动机是在一个字符串上建,那么广义后缀自动机就是在一棵trie上建的。广义后缀自动机的构建和普通后缀自动机很相似,都是把每个节点接在它前面的前缀节点上,但是这里有一个地方和普通的后缀自动机不同,就是它前面的前缀节点可能已经有一个向当前要插入字符的转移了,令这个前缀节点为 p p ,转移到的点为q,这时候我们分两种情况讨论:
一、 len[p]+1=len[q] l e n [ p ] + 1 = l e n [ q ] ,那么我们不必新建任何节点,直接把 q q 作为下一步的前缀节点即可。
二、len[p]+1<len[q],那么我们需要复制一个节点 nq n q ,并令 len[nq]=len[p]+1 l e n [ n q ] = l e n [ p ] + 1 ,把 q q 的后缀链接和转移复制给它,然后将q的后缀链接指向 nq n q ,最后我们把 nq n q 作为下一步的前缀节点。
我们发现上述的分类讨论和构建普通后缀自动机时的那个分类讨论非常相似,实际上这两个分类讨论都是为了维护后缀自动机的性质,把一个点拆成一个可接受新状态的点和一个不可接受新状态的点。理解了这一点后,就能更好地理解后缀自动机和广义后缀自动机了。
那么我们回到这一题,我们以每个叶子节点为根,我们发现树的结构就是一棵trie,那么直接建广义自动机,最后就可以求出本质不同的子串数目了。根据后缀自动机的性质,这个数目为 (len[i]len[pre[i]]) ∑ ( l e n [ i ] − l e n [ p r e [ i ] ] ) pre[i] p r e [ i ] i i 的后缀链接指向的点)。以上算法的总时间复杂度为O(20n),可以通过此题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,c,val[100010],d[100010]={0},first[100010]={0},tot=0;
ll len[4000010];
int totp=0,last,pre[4000010],ch[4000010][10]={0};
struct edge
{
    int v,next;
}e[200010];

void insert(int a,int b)
{
    e[++tot].v=b;
    e[tot].next=first[a];
    first[a]=tot;
}

void clone_point(int &nq,int x,int p,int q)
{
    nq=++tot;
    len[nq]=len[p]+1;
    pre[nq]=pre[q];
    for(int i=0;i<c;i++)
        ch[nq][i]=ch[q][i];
    while(p&&ch[p][x]==q) ch[p][x]=nq,p=pre[p];
    pre[q]=nq;
}

void extend(int x)
{
    int p,q,np,nq;
    if (ch[last][x])
    {
        if (len[last]+1==len[ch[last][x]]) last=ch[last][x];
        else
        {
            clone_point(nq,x,last,ch[last][x]);
            last=nq;
        }
    }
    else
    {
        np=++tot;
        len[np]=len[last]+1;
        p=last;
        while(p&&!ch[p][x]) ch[p][x]=np,p=pre[p];
        if (!p) pre[np]=1;
        else
        {
            q=ch[p][x];
            if (len[p]+1==len[q]) pre[np]=q;
            else
            {
                clone_point(nq,x,p,q);
                pre[np]=nq;
            }
        }
        last=np;
    }
}

void build(int v,int f)
{
    extend(val[v]);
    int now=last;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=f)
        {
            build(e[i].v,v);
            last=now;
        }
}

int main()
{
    scanf("%d%d",&n,&c);
    for(int i=1;i<=n;i++)
        scanf("%d",&val[i]);
    for(int i=1;i<n;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        insert(a,b),insert(b,a);
        d[a]++,d[b]++;
    }

    len[1]=pre[1]=0;
    for(int i=1;i<=n;i++)
        if (d[i]==1)
        {
            last=1;
            build(i,0);
        }

    ll ans=0;
    for(int i=2;i<=tot;i++)
        ans+=len[i]-len[pre[i]];
    printf("%lld",ans);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值