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

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