解题思路:
题目要求的即是在trie树上建立sam后,本质不同的子串个数。
又由于从不同叶子的方向有不一样的串,所以要把以不同叶子为根的20棵tried树合并为一个大tried树来做即可。
这里有两种求本质不同的子串个数的方法:
1.每条路径走到每个点都是一个串,所以我们只需要统计出往每个点走之后都有多少串(用f[i]表示)就好了,这种做法要先按max值拓扑排序后反拓扑序计算。
即是
f[i]=∑f[son[i]]+1
f
[
i
]
=
∑
f
[
s
o
n
[
i
]
]
+
1
2.对于每个节点x,它贡献的子串数量是
max[x]−min[x]+1
m
a
x
[
x
]
−
m
i
n
[
x
]
+
1
,又因为
min[x]=max[fa]+1
m
i
n
[
x
]
=
m
a
x
[
f
a
]
+
1
,则
ans=∑max[x]−max[fa]
a
n
s
=
∑
m
a
x
[
x
]
−
m
a
x
[
f
a
]
,这种方法要方便一点。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int getint()
{
int i=0,f=1;char c;
for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
if(c=='-')f=-1,c=getchar();
for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
return i*f;
}
const int N=100005,M=20*N;
int n,m,col[N],du[N];
int tot,first[N],nxt[N<<1],to[N<<1];
struct SAM
{
int tot,last,son[M][10],fa[M],mx[M],c[M],q[M];
ll f[M];
SAM(){last=++tot;}
inline void extend(int p,int c)
{
int np=last=++tot;mx[np]=mx[p]+1;
while(p&&!son[p][c])son[p][c]=np,p=fa[p];
if(!p)fa[np]=1;
else
{
int q=son[p][c];
if(mx[q]==mx[p]+1)fa[np]=q;
else
{
int nq=++tot;mx[nq]=mx[p]+1;
memcpy(son[nq],son[q],sizeof(son[q]));
fa[nq]=fa[q],fa[q]=fa[np]=nq;
while(p&&son[p][c]==q)son[p][c]=nq,p=fa[p];
}
}
}
inline void calc()
{
for(int i=1;i<=tot;i++)f[i]=1,c[mx[i]]++;
for(int i=1;i<=n;i++)c[i]+=c[i-1];
for(int i=tot;i;i--)q[c[mx[i]]--]=i;
for(int i=tot;i;i--)
for(int j=0;j<m;j++)
if(son[q[i]][j])f[q[i]]+=f[son[q[i]][j]];
/*for(int i=1;i<=tot;i++)ans+=mx[i]-mx[fa[i]];第二种方法*/
}
}sam;
void add(int x,int y)
{
nxt[++tot]=first[x],first[x]=tot,to[tot]=y,du[y]++;
}
void dfs(int u,int fa,int p)
{
sam.extend(p,col[u]);
int np=sam.last;
for(int e=first[u];e;e=nxt[e])
{
int v=to[e];
if(v==fa)continue;
dfs(v,u,np);
}
}
int main()
{
//freopen("lx.in","r",stdin);
n=getint(),m=getint();
for(int i=1;i<=n;i++)col[i]=getint();
for(int i=1;i<n;i++)
{
int x=getint(),y=getint();
add(x,y),add(y,x);
}
for(int i=1;i<=n;i++)
if(du[i]==1)dfs(i,0,1);
sam.calc();
printf("%lld",sam.f[1]-1);
return 0;
}