您的位置:首页 > 其它

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

2018-04-03 16:57 302 查看
测试地址:诸神眷顾的幻想乡

做法:本题需要用到广义后缀自动机。

仔细读题,我们发现树的叶子节点非常少,只有2020个,那么我们把从一个叶子节点到另一个叶子节点所经过的串列出来,我们发现这些串包含树上的每条路径,证明是显然的。

接下来出现了一个问题,后缀自动机是处理单个串的子串的一个数据结构,现在我们有那么多个串,用后缀自动机好像没办法做?所以我们需要用到后缀自动机的扩展——广义后缀自动机。

如果说普通的后缀自动机是在一个字符串上建,那么广义后缀自动机就是在一棵trie上建的。广义后缀自动机的构建和普通后缀自动机很相似,都是把每个节点接在它前面的前缀节点上,但是这里有一个地方和普通的后缀自动机不同,就是它前面的前缀节点可能已经有一个向当前要插入字符的转移了,令这个前缀节点为pp,转移到的点为qq,这时候我们分两种情况讨论:

一、len[p]+1=len[q]len[p]+1=len[q],那么我们不必新建任何节点,直接把qq作为下一步的前缀节点即可。

二、len[p]+1<len[q]len[p]+1<len[q],那么我们需要复制一个节点nqnq,并令len[nq]=len[p]+1len[nq]=len[p]+1,把qq的后缀链接和转移复制给它,然后将qq的后缀链接指向nqnq,最后我们把nqnq作为下一步的前缀节点。

我们发现上述的分类讨论和构建普通后缀自动机时的那个分类讨论非常相似,实际上这两个分类讨论都是为了维护后缀自动机的性质,把一个点拆成一个可接受新状态的点和一个不可接受新状态的点。理解了这一点后,就能更好地理解后缀自动机和广义后缀自动机了。

那么我们回到这一题,我们以每个叶子节点为根,我们发现树的结构就是一棵trie,那么直接建广义自动机,最后就可以求出本质不同的子串数目了。根据后缀自动机的性质,这个数目为∑(len[i]−len[pre[i]])∑(len[i]−len[pre[i]])(pre[i]pre[i]为ii的后缀链接指向的点)。以上算法的总时间复杂度为O(20n)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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: