【Prufer数列/组合数学】[HNOI2008][HYSBZ/BZOJ1005]明明的烦恼
题目链接
分析
Prufer数列
生成Prufer数列
由一棵树得到它的 Prufer Sequence 总共需要 n-2 步,每一步都在当前的树中寻找具有最小标号的叶子节点(度为 1),将与其相连的点的标号设为 Prufer Sequence 的第 i 个元素,并将此叶子节点从树中删除,直到最后得到一个长度为 n-2 的 Prufer Sequence 和一个只有两个节点的树。
所以一个树,只能得到一个唯一的 Prufer Sequence。
由Prufer数列生成树
先将所有编号为 1 到 n 的点的度赋初值为 1,然后加上它在 Prufer Sequence 中出现的次数,得到每个点的度
先执行 n-2 步,每一步,选取具有最小标号的度为 1 的点 u 与 Prufer Sequence 中的第 i 个数 v 表示的顶点相连,得到树中的一条边,并将 u 和 v 的度减1
最后再把剩下的两个度为 1 的点连边,加入到树中
所以Prufer Sequence和一个树唯一对应。
得到
定理:一个 Prufer Sequence 和一棵树一一对应
如果还不不清楚,请自行百度
回到这道题
一个点在 Prufer Sequence 出现的次数等于它的度数减一,我们考虑有确定度数的点,给他们编号1~cnt,第i个点的度数为
如果没有不确定度数的点,那么方案数为全排列。
然而有不确定的点那么,这些确定的点会放在n-2个位置中的sum个,所以
剩下的n-2-sum个位置,每个位置都可以从n-cnt个点中任意选择一个来放。
用高精度计算,除法不好做,可先分解质因数。
代码
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define MAXN 1000
#define MAXLEN 3000
int n,d[MAXN+10],p[MAXN+10],pcnt,fcnt[MAXN+10],cnt,sum;
bool f[MAXN+10];
void prepare(){
int i,j;
for(i=2;i<=n;i++){
if(!f[i])
p[++pcnt]=i;
for(j=1;p[j]*i<=n;j++){
f[i*p[j]]=1;
if(i%p[j]==0)
break;
}
}
}
struct hp{
int a[MAXLEN+10];
inline hp(){
memset(a,0,sizeof a);
}
inline hp(hp &a){
*this=a;
}
void int2hp(int b){
while(b)
a[++a[0]]=b%10,b/=10;
if(!a[0])
a[0]++;
}
inline hp(int b){
memset(a,0,sizeof a);
int2hp(b);
}
void operator*=(const hp &b){
hp c;
int len=a[0]+b.a[0],i,j;
for(i=1;i<=a[0];i++)
for(j=1;j<=b.a[0];j++)
c.a[i+j-1]+=a[i]*b.a[j];
for(i=1;i<=len;i++){
c.a[i+1]+=c.a[i]/10;
c.a[i]%=10;
}
while(!c.a[len]&&len)
len--;
c.a[0]=len;
*this=c;
}
void print(){
for(int i=a[0];i;i--)
printf("%d",a[i]);
}
}ans(1);
void Read(int &x){
char c;
bool f=0;
while(c=getchar(),c!=EOF){
if(c=='-')
f=1;
if(c>='0'&&c<='9'){
x=c-'0';
while(c=getchar(),c>='0'&&c<='9')
x=x*10+c-'0';
ungetc(c,stdin);
if(f)
x=-x;
return;
}
}
}
void read(){
Read(n);
if(n==1){
Read(d[1]);
if(d[1]<=0)
puts("1");
else
puts("0");
exit(0);
}
for(int i=1;i<=n;i++){
Read(d[i]);
if(d[i]>=n||!d[i]){
puts("0");
exit(0);
}
if(d[i]>-1)
sum+=d[i]-1,cnt++;
}
if(sum>n-2){
puts("0");
exit(0);
}
}
void de_factor(int n,int d){
int i,t=sqrt(n+0.5);
for(i=1;p[i]<=t&&n>1;i++)
while(n%p[i]==0)
fcnt[p[i]]+=d,n/=p[i];
if(n>1)
fcnt[n]+=d;
}
hp quick_pow(int a,int b){
hp c(a),ret;
ret.a[0]=1,ret.a[1]=1;
while(b){
if(b&1)
ret*=c;
c*=c;
b>>=1;
}
return ret;
}
void solve(){
int i,j;
for(i=n-2;i>n-2-sum;i--)
de_factor(i,1);
for(i=1;i<=n;i++)
if(d[i]>1)
for(j=d[i]-1;j;j--)
de_factor(j,-1);
for(i=1;i<=pcnt;i++)
ans*=quick_pow(p[i],fcnt[p[i]]);
ans*=quick_pow(n-cnt,n-2-sum);
}
int main()
{
read();
prepare();
solve();
ans.print();
}