版权属于XP,想要引用此题(包括题面)的朋友请联系博主
分析:
假如我们从
n
n
个点钟选出了个点,那么这
s
s
个点的合法连通状态一定是一棵树
每个结点都有一个最大度数(也就是说每个结点连出的边数)
<=
<=
<script type="math/tex" id="MathJax-Element-12"><=</script>的限制比较烦,我们先看
=ai
=
a
i
的情况
问题转化成:已知s个点,每个点的度数 ai a i ,求能形成的不同形态树的数量
关于这个问题,有一个有力的工具:Prufer定理
Prufer数列是无根树的一种数列
在组合数学中,Prufer数列是由一个对于顶点标过号的树转化来的数列,点数为n的树转化来的Prufer数列长度为n-2
Prufer数列的生成
找出这棵树的当前叶子结点中标号最小的那个,把与ta相连的点的编号加入
prufer
p
r
u
f
e
r
数列,然后删除这个叶子结点
操作到最后剩下两个结点时,这两个结点会被同时删除
举个例子:
Prufer数列转化成数
每次找到当前prufer数列中未出现的编号中最小的编号,把ta对应的点和当前prufer数列中开头的点连接,然后删除数列开头的点
Prufer数列的性质
一棵n个结点的有编号无根树的prufer数列长度为n-2
显然,每次删除一个结点就会在数列中加入一个数,直到剩最后两个结点的ta们同时被删除,数列中自然有n-2个数原树中结点度数为 di d i 的结点编号在prufer数列中出现 di−1 d i − 1 次
由于一个结点在作为叶子结点删除时,ta的当前度数为1,而每删除一个与ta相邻的叶子结点时,ta的编号会被计入一次,且ta的度数会-1,所以ta最终会被记录 di−1 d i − 1 次,然后作为叶子结点被删除每个prufer数列都和一棵有编号无根树意义对应
由于在对有标号无根树进行操作时,每一步的操作都是固定的(选取最小标号叶子节点),所以每棵无根树只对应唯一一个prufer数列
由于prufer数列在还原成树时每步操作也是唯一的,所以每个prufer数列只能还原成唯一的一棵树
既然prufer数列和有编号无根树一一对应,那么我们就可以把有编号无根树转为prufer数列计数
假如我们选出的点度数分别为
a1,a2,...,as
a
1
,
a
2
,
.
.
.
,
a
s
,那么每个点的出现次数就分别为
a1−1,a2−1,...,as−1
a
1
−
1
,
a
2
−
1
,
.
.
.
,
a
s
−
1
得到一个
s−2
s
−
2
的序列,我们可以把这
s−2
s
−
2
个数任意排列,不同的排列就对应着该情况下的一种合法树,即:
注意:上面讨论的都是确定结点度数的情况
设计状态
f[i][j][k]
f
[
i
]
[
j
]
[
k
]
表示前
i
i
个点,选了个点,当前的
prufer
p
r
u
f
e
r
数列长度为
k
k
时的方案数
如果当前这个点不选:
如果选择当前点,枚举这个点在序列中出现的次数(度数-1):
f[i][j][k]=∑ai−1d=0f[i−1][j−1][k−d](k>=d)
f
[
i
]
[
j
]
[
k
]
=
∑
d
=
0
a
i
−
1
f
[
i
−
1
]
[
j
−
1
]
[
k
−
d
]
(
k
>=
d
)
知道了每个数在prufer数列中出现的次数,那么不同的prufer数列个数就好求了
设
cnti
c
n
t
i
为第
i
i
个数字在prufer数列中出现的次数,为当前选择的元素个数
然而我们在转移的时候,并没有记录
cnt
c
n
t
所以我们不要在最后处理,直接在状态转移的时候维护上式
因此,最后答案: f[n][p][p−2]∗(p−2)! f [ n ] [ p ] [ p − 2 ] ∗ ( p − 2 ) !
时间复杂度:
O(n4)
O
(
n
4
)
(因为不合法状态比较多,对于
n<=100
n
<=
100
的数据,
n4
n
4
的复杂度还是可以过的)
tip
注意代码中转移的写法
i,j,k
i
,
j
,
k
的上界都是n。。。我一开始
j−>i,k−>j
j
−
>
i
,
k
−
>
j
就狂WA不止
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const ll p=19260817;
const int N=105;
int n,a[N];
ll f[N][N][N],jc[N],inv[N];
ll KSM(ll a,ll b) {
ll t=1;
while (b) {
if (b&1) t=(t*a)%p;
b>>=1;
a=(a*a)%p;
}
return t%p;
}
void solve()
{
f[0][0][0]=1;
for (int i=0;i<n;i++)
for (int j=0;j<n;j++)
for (int k=0;k<n;k++) if (f[i][j][k]) {
f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%p;
for (int d=0;d<a[i+1]&&d+k<=n-2;d++)
f[i+1][j+1][k+d]=(f[i+1][j+1][k+d]+f[i][j][k]*inv[d]%p)%p;
}
}
int main()
{
scanf("%d",&n);
jc[0]=1;
for (int i=1;i<=100;i++) jc[i]=jc[i-1]*(ll)i%p;
for (int i=0;i<=100;i++) inv[i]=KSM(jc[i],p-2)%p;
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
solve();
printf("%d ",n);
for (int i=2;i<=n;i++) {
ll ans=f[n][i][i-2]*jc[i-2]%p;
printf("%lld ",ans);
}
}