分析:
题目所求的就是一个叫:有标号无根树 的数目
我们怎么知道两棵树的形态是否一样呢?
换句话说,我们能不能找到一种表示树的方法,使得不同的树表示出来互不相同呢?
那么我们就需要一个新姿势了:Prufer数列
Prufer数列是无根树的一种数列
在组合数学中,Prufer数列是由一个对于顶点标过号的树转化来的数列,点数为n的树转化来的Prufer数列长度为n-2
Prufer数列的生成
找出这棵树的当前叶子结点中标号最小的那个,把与ta相连的点的编号加入
prufer
p
r
u
f
e
r
数列,然后删除这个叶子结点
操作到最后剩下两个结点时,这两个结点会被同时删除
举个例子:
Prufer数列转化成数
每次找到当前prufer数列中未出现的编号中最小的编号,把ta对应的点和当前prufer数列中开头的点连接,然后删除数列开头的点
Prufer数列的性质
一棵 n n 个结点的有编号无根树的prufer数列长度为
显然,每次删除一个结点就会在数列中加入一个数,直到剩最后两个结点的ta们同时被删除,数列中自然有 n−2 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数列只能还原成唯一的一棵树
对于已确定度数的点(
cnt
c
n
t
个),我们可以计算出ta们出现的次数之和:
sum=∑cnti=1(di−1)
s
u
m
=
∑
i
=
1
c
n
t
(
d
i
−
1
)
那么这
sum
s
u
m
个元素,在长度为
n−2
n
−
2
的序列中的不同排列数为:
(在
n−2
n
−
2
中选择
sum
s
u
m
个位置,这些位置上进行排序)
剩下的
n−2−sum
n
−
2
−
s
u
m
个空位可以随意排列
n−cnt
n
−
c
n
t
个点
式子化一下:
但是这个数实在是太大了,又没有模数
我们只能选择高精度
后面的可以重定义乘法,直接用快速幂
但是前面的除法就有点难受了,怎么破?
我们可以把所有数质因数分解,最后把质因数乘起来即可(高乘低)
无解情况: sum>n−2,di=0 s u m > n − 2 , d i = 0
tip
一开始WA了,好像是高精度写错了
后来才发现,因为数太大了,高精度的位数高达1W
注意,只有全局变量不用初始化,局部变量一定要初始化
又T了(时间卡太紧)
memset太费时,去掉
申请结点费事,去掉
三部分都分解质因数费时,只分解
(n−2)!(n−2−sum)!∗∏cnti=1(di−1)!
(
n
−
2
)
!
(
n
−
2
−
s
u
m
)
!
∗
∏
i
=
1
c
n
t
(
d
i
−
1
)
!
最后变成这个熊样,A掉了
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1005;
int sshu[N],n,d[N],sum=0,cnt=0,tot=0;
struct node{
int data[100000],len;
};
node ans;
void mult(node &a,int x) { //高乘低
for (int i=1;i<=a.len;i++) a.data[i]*=x;
for (int i=1;i<=a.len;i++) {
a.data[i+1]+=a.data[i]/10;
a.data[i]%=10;
}
while (a.data[ans.len+1]) {
a.len++;
a.data[a.len+1]+=a.data[a.len]/10;
a.data[a.len]%=10;
}
}
void cl(int x,int z) {
if (x<=1) return;
for (int i=2;i*i<=x;i++)
while (x%i==0) x/=i,sshu[i]+=z;
if (x!=1) sshu[x]+=z;
}
int main()
{
scanf("%d",&n);
if (n==1) {
int x;
scanf("%d",&x);
if (x==0||x==-1) printf("1\n");
else printf("0\n");
return 0;
}
for (int i=1;i<=n;i++) {
int x;
scanf("%d",&x);
if (!x) {
printf("0\n");
return 0;
}
else if (x!=-1) sum+=x-1,d[++tot]=x-1;
else cnt++; //未知度数的结点数
}
if (sum>n-2) {
printf("0\n");
return 0;
}
for (int i=n-2;i>n-2-sum;i--) cl(i,1);
for (int i=1;i<=tot;i++)
for (int j=2;j<=d[i];j++) cl(j,-1);
ans.data[1]=1; ans.len=1;
for (int i=2;i<=n;i++)
while (sshu[i]) mult(ans,i),sshu[i]--;
for (int i=1;i<=n-sum-2;i++) mult(ans,cnt);
for (int i=ans.len;i>=1;i--) printf("%d",ans.data[i]);
return 0;
}