1005: [HNOI2008]明明的烦恼
Time Limit: 1 Sec
Memory Limit: 162 MB
Description
自从明明学了树的结构,就对奇怪的树产生了兴趣……给出标号为1到N的点,以及某些点最终的度数,允许在
任意两点间连线,可产生多少棵度数满足要求的树?
Input
第一行为N(0 < N < = 1000),
接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1
Output
一个整数,表示不同的满足要求的树的个数,无解输出0
Sample Input
3
1
-1
-1
Sample Output
2
HINT
两棵树分别为1-2-3;1-3-2
解答这道题需要用到一个叫Perfur Sequence(以下简称PS)的知识,对于一颗有n个节点的树,它与一个长度为n-2的PS一一对应,那么它们的对应方式如何呢?
将一棵树转化为PS:
假设我们有一颗树,其中有如下边:1-2,1-3,2-4,2-5,3-6,3-7(其实就是节点标号1~7的完全二叉树),我们先取标号最小的叶子节点4,记录此时与其相连的节点,得到了PS的第一位数字:2,然后删除节点4。按照上面的步骤,取5得2,取2得1,取1得3,取6得3。我们这个时候就得到了这个长为5的PS:2 2 1 3 3,显然,对于一棵树只能得到一个PS,也就是每棵树只对应一个PS。
将一个PS转化为树:
使用上面的PS:2 2 1 3 3 。首先这是一个长为5的PS,那就意味这这棵树有7个节点,显然由上面我们可以知道每个节点的度数=它在PS中出现过的次数+1 。于是我们对于每一个节点建立一个表来描述它们的权值:
1 2 3 4 5 6 7
2 3 3 1 1 1 1
此时取度数为1的标号最小的节点4,使其与PS中的第一位数字对应标号的节点2相连,建边4-2,然后删除PS中第一位数字2,此时PS为2 1 3 3,同时在表中将2和4的权值分别减去1:
1 2 3 4 5 6 7
2 2 3 0 1 1 1
节点5与节点2相连,建边5-2,PS变为1 3 3,权值表变为:
1 2 3 4 5 6 7
2 1 3 0 0 1 1
节点2与节点1相连,建边2-1,PS变为3 3,权值表变为:
1 2 3 4 5 6 7
1 0 3 0 0 1 1
节点1与节点3相连,建边1-3,PS变为3,权值表变为:
1 2 3 4 5 6 7
0 0 2 0 0 1 1
节点6与节点3相连,建边6-3,PS清空,权值表变为:
1 2 3 4 5 6 7
0 0 1 0 0 0 1
最后在连接节点3与节点7,得到下面这些边:4-2、5-2、2-1、1-3、6-3、7-3,很容易看出这就是我们上面的那颗树。
在转换过程中我们能发现,PS编码生成的树是唯一的,结合上面的结论,我们得出了PS编码的性质:一个PS编码和一棵树一一对应。
有了这个知识后,我们该如何解决这个问题呢?
首先,无解的情况很好判断,这里就不过多说明了。
对于有解的情况,由上面的过程我们知道,一个节点的度数-1等于它在PS中出现过的次数,假设我们有m个度数有限制的点,分别为d[i]。
设:
sum=∑(d[i]-1),(1<=i<=m)。
那么对于一个PS,不同排列的种数为:
c(m,n-2)*((sum!)/(∏(d[i]-1)!)),(1<=i<=m)。
对于剩下的n-2-m个位置,我们就可以随意排列剩下的n-m个点,于是总的方案就是:
c(m,n-2)*((sum!)/(∏(d[i]-1)!))*(n-m)^(n-2-m),(1<=i<=m)
这就是有解的情况下的答案,高精度是必须的。
代码:
#include<bits/stdc++.h>
#define mod 1000000
#define ll long long
using namespace std;
int n,m,tot,cnt,d[1005],num[1005],pri[1005],ans[1005],l=1;
bool jud(int x)
{
for (int i=2 ; i<=sqrt(x) ; i++)
if(x%i==0)
return 0;
return 1;
}
void getpri()
{
for (int i=2 ; i<=1000 ; i++)
if(jud(i))
pri[++cnt]=i;
}
void solve(int a,int f)
{
for (int k=1 ; k<=a ; k++)
{
int x=k;
for (int i=1 ; i<=cnt ; i++)
{
if (x<=1)
break;
while (x%pri[i]==0)
{
num[i]+=f;
x/=pri[i];
}
}
}
}
void mul(int x)
{
for (int i=1 ; i<=l ; i++)
ans[i]*=x;
for (int i=1 ; i<=l ; i++)
{
ans[i+1]+=ans[i]/mod;
ans[i]%=mod;
}
while (ans[l+1]>0)
{
l++;
ans[l+1]+=ans[l]/mod;
ans[l]%=mod;
}
}
int main()
{
getpri();
ans[1]=1;
scanf("%d",&n);
if (n==1)
{
int x;
scanf("%d",&x);
if(!x)
printf("1");
else
printf("0");
return 0;
}
for (int i=1 ; i<=n ; i++)
{
scanf("%d",&d[i]);
if (!d[i])
{
printf("0");
return 0;
}
if (d[i]==-1)
m++;
else
{
d[i]--;
tot+=d[i];
}
}
if(tot>n-2)
{
printf("0");
return 0;
}
solve(n-2,1);
solve(n-2-tot,-1);
for(int i=1;i<=n;i++)
if(d[i])
solve(d[i],-1);
for(int i=1;i<=cnt;i++)
while(num[i]--)
mul(pri[i]);
for(int i=1;i<=n-2-tot;i++)
mul(m);
for(int i=l;i>0;i--)
if(i==l)
printf("%d",ans[i]);
else
printf("%06d",ans[i]);
return 0;
}