分析:
先强调一点,题目中特意指出:太阳花田的结构比较特殊,只与一个空地相邻的空地数量不超过20个
翻译一下:最多有20个叶结点
知道这道题是用广义后缀自动机解决
广义
SAM
中包含了所有互异的子串
所以我们应该把尽量少,但是包含所有情况的字符串扔进
SAM
中
一开始我想的比较简单,只要把所有叶结点到根的字符串扔进去就可以了
但是这样显然包括所有的字符串情况(画个图就知道了,没法处理折线)
所以最后的解决方法就是把叶结点两两配对,
得到的字符串扔进
SAM
中
简单的做法:枚举每一个叶结点,以此为根,得到根到结点的路径,扔进 SAM 即可
最后的答案:
∑ni=1dis[i]−dis[fa[i]]
意思就是从
parenti
走到
i
有多少种不同的方式(
tip
在以某一个结点为根dfs的时候,我们不用把每一个字符串记录下来再
我们可以在dfs的过程中,直接insert结点即可
但是要注意插入不同的结点时,
last
是不一样的
所以我们需要专门记录一下
起始时: last=root
第一次写,insert操作比较容易写错
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int N=100010;
int last=1,root=1,sz=1,ch[N*40][11],dis[N*40],fa[N*40];
int V[N],n,c,st[N],tot=0,in[N];
struct node{
int y,nxt;
};
node way[N<<1];
void add(int u,int w)
{
tot++;
way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
tot++;
way[tot].y=u;way[tot].nxt=st[w];st[w]=tot;
}
void insert(int x)
{
int now=ch[last][x],pre=last;
if (now)
{
if (dis[now]==dis[pre]+1) last=now;
else
{
int nows=++sz;
dis[nows]=dis[pre]+1;
memcpy(ch[nows],ch[now],sizeof(ch[now]));
fa[nows]=fa[now]; fa[now]=nows;
for (;pre&&ch[pre][x]==now;pre=fa[pre]) ch[pre][x]=nows;
last=nows;
}
}
else
{
now=++sz;
dis[now]=dis[pre]+1; last=now;
for (;pre&&!ch[pre][x];pre=fa[pre]) ch[pre][x]=now;
if (!pre) fa[now]=root;
else
{
int q=ch[pre][x];
if (dis[q]==dis[pre]+1) fa[now]=q;
else
{
int nows=++sz;
dis[nows]=dis[pre]+1;
memcpy(ch[nows],ch[q],sizeof(ch[q]));
fa[nows]=fa[q]; fa[q]=fa[now]=nows;
for (;pre&&ch[pre][x]==q;pre=fa[pre]) ch[pre][x]=nows;
}
}
}
}
void dfs(int now,int fa)
{
insert(V[now]);
int t=last; //我们不用从头构造,只要记录一下在SAM上走到了哪里即可
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=fa)
dfs(way[i].y,now),last=t;
}
int main()
{
scanf("%d%d",&n,&c);
for (int i=1;i<=n;i++) scanf("%d",&V[i]);
for (int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y); in[x]++; in[y]++;
}
for (int i=1;i<=n;i++)
if (in[i]==1)
last=root,dfs(i,0);
ll ans=0;
for (int i=1;i<=sz;i++)
ans+=(ll)dis[i]-dis[fa[i]];
printf("%lld\n",ans);
return 0;
}