prufer序学习小记+2020牛客暑期多校训练营(第七场)I Valuable Forests
Posted ZLTJohn
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了prufer序学习小记+2020牛客暑期多校训练营(第七场)I Valuable Forests相关的知识,希望对你有一定的参考价值。
prufer序与无根树的关系
对于一颗带标号无根树,定义其prufer序为按照如下规则生成的序列:
进行n-2步,对于第i步,选择度为1且编号最小的点x,将边的另一端点标号y作为序列的第i位,并删去x和边。
若长度为n-2的序列a[1~n-2]满足:任意a[i]∈[1,n],那么该序列为某棵树对应的prufer序,可按照如下方法根据prufer序构造无根树,且该无根树对应的prufer序为a:
设初始点集V=1…n,先进行n-2步:第i步取a[i],并在V-∪a[i~n-2]中取编号最小的元素x,为a[i]与x连边,并删去V中的x。
最后一步,对V中剩下两节点连边。
样例随便画一棵树
定理:不同的带标号无根树得出的prufer序不同。
推论: f : 树->a[1~n-2] | ∀a[i]∈[1,n] 就是一个单射,又因为a[]必定能对应回原本的树,所以他们之间存在一一映射的关系。
可以证明该推论的一个必要条件成立,帮助理解:
合法序列a[]必定对应一颗树而不是一般图:从上面的构造法可以看出,V中每个点都有连边,且总边数为n-1,只能是一棵树。若不是,必定存在一个点没有任何连边,与构造法矛盾。
性质1:大小为n的带标号无根树有n^(n-2)个
由于prufer序有n^(n-2)种,对应的树也有这么多。
性质2:度数为d的节点在prufer序中出现d-1次
因为一个点只剩一个度数的时候是不会再被计入prufer序内的。
性质3:给定点度数d[1~n]可以算出合法无根树数量
简单计数
c
n
t
=
(
n
−
2
)
!
∏
(
d
[
i
]
−
1
)
!
cnt=\\frac(n-2)!\\prod (d[i]-1)!
cnt=∏(d[i]−1)!(n−2)!
根据这些性质可以解决一些计数问题。
Valuable Forests
我们先求一颗大小为1~n的树的贡献,再组合成森林求贡献。
对于一个大小为n的树,所有合法情况的度数平方和f[n]是多少呢?
考虑每一种prufer序,贡献就是
∑
a
[
]
∑
x
=
1...
n
(
a
p
p
e
a
r
a
[
]
[
x
]
+
1
)
2
\\sum_a[]\\sum_x=1...n (appear_a[][x]+1)^2
a[]∑x=1...n∑(appeara[][x]+1)2
appear_a代表x在prufer序a里出现几次。
我们可以分开统计每个点的贡献,这是经典套路了。具体地,枚举x的度数,计算这种情况下对应的a[]的方案数。又因为每个点是等价的,那么总答案变成
f
[
n
]
=
n
∑
j
=
1..
n
−
1
j
2
C
n
−
2
j
−
1
(
n
−
1
)
n
−
2
−
(
j
−
1
)
f[n]=n\\sum_j=1..n-1j^2C_n-2^j-1(n-1)^n-2-(j-1)
f[n]=nj=1..n−1∑j2Cn−2j−1(n−1)n−2−(j−1)
j枚举了点x的度数。
现在我们得到了f[1~n],怎么计算森林呢?
设大小为n的森林的答案是g[n],要得到他,我们可以枚举n号点形成的树的大小i(如果随便选i个点就会计重),再利用g[1~n]得到答案。
g
[
n
]
=
∑
i
=
1..
n
C
n
−
1
i
−
1
(
g
[
n
−
i
]
∗
i
i
−
2
+
c
n
t
[
n
−
i
]
∗
f
[
i
]
)
g[n]=\\sum_i=1..nC_n-1^i-1(g[n-i]*i^i-2+cnt[n-i]*f[i])
g[n]=i=1..n∑Cn−1i−1(g[n−i]∗ii−2+cnt[n−i]∗f[i])
其中cnt[n-i]代表n-i个点形成的森林数。
当我把n-i的森林和i的树拼起来的时候,方案数就变成了cnt[n-i]*i^(i-2),原本森林的贡献和的方案数是cnt[n-i],但现在方案变大了,要乘上放大倍数。反过来树的贡献和也要乘,这样就能得到新方案数下的贡献和了。
c
n
t
[
n
]
=
∑
i
=
1..
n
C
n
−
1
i
−
1
c
n
t
[
n
−
i
]
i
i
−
2
cnt[n]=\\sum_i=1..nC_n-1^i-1cnt[n-i]i^i-2
cnt[n]=i=1..n∑Cn−1i−1cnt[n−i]ii−2
代码
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
typedef long long ll;
typedef double db;
const int N=5e3+5,M=1e6+5;
ll f[N],cnt[N],g[N],pwv;
int c[N][N],pw[N][N],n,m,i,mo;
void predo(int n)
int i,j;
c[0][0]=1;
pw[0][0]=1;
fo(i,1,n)
c[i][0]=1;
pw[i][0]=1;
fo(j,1,n)
c[i][j]=(c[i-1][j-1]+c[i-1][j])%mo;
pw[i][j]=pw[i][j-1]*(ll)i%mo;
void predp(int nn)
int n,i,j;
fo(n,1,nn)
fo(j,1,n-1)
f[n]=(f[n]+1ll*j*j%mo*c[n-2][j-1]%mo*pw[n-1][n-2-(j-1)])%mo;
f[n]=f[n]*n%mo;
cnt[0]=1;
fo(n,1,nn)
fo(i,1,n)//mind i-2<0
if (i>1) pwv=pw[i][i-2];else pwv=1;
cnt[n]=(cnt[n]+(ll)c[n-1][i-1]*cnt[n-i]%mo*pwv)%mo;
g[0]=0;
fo(n,1,nn)
fo(i,1,n)
if (i>1) pwv=pw[i][i-2];else pwv=1;
g[n]=(g[n]+((ll)g[n-i]*pwv+(ll)cnt[n-i]*f[i])%mo*c[n-1][i-1])%mo;
int main()
freopen("I.in","r",stdin);
//freopen("B.out","w",stdout);
scanf("%d %d",&m,&mo);
predo(5e3);
predp(5e3);
fo(i,1,m)
scanf("%d",&n);
printf("%lld\\n",g[n]);
以上是关于prufer序学习小记+2020牛客暑期多校训练营(第七场)I Valuable Forests的主要内容,如果未能解决你的问题,请参考以下文章
2021牛客暑期多校训练营7 F.xay loves trees 主席树+dfs序