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;
}