膜拜PoPoQQQ大爷,题解
树的Prufer编码,以下内容摘自度娘:
Prufer数列是无根树的一种数列。在组合数学中,Prufer数列由有一个对于顶点标过号的树转化来的数列,点数为n的树转化来的Prufer数列长度为n-2。它可以通过简单的迭代方法计算出来,一个树对应一个Prufer数列。
Prufer序列显然满足一个性质:一个点若度数为d,则一定在Prufer序列中出现了d-1次
于是这就变成了一个排列组合的问题了
令每个已知度数的节点的度数为di,有n个节点,m个节点未知度数,left=(n-2)-(d1-1)-(d2-1)-...-(dk-1)
已知度数的节点可能的组合方式如下
(n-2)!/(d1-1)!/(d2-1)!/.../(dk-1)!/left!
剩余left个位置由未知度数的节点随意填补,方案数为m^left
于是最后有 ans=(n-2)!/(d1-1)!/(d2-1)!/.../(dk-1)!/left!* m^left
答案很显然要高精度,为了避免高精度除法我们可以对每个阶乘暴力分解质因数,对指数进行加减操作即可。
/**************************************************************
Problem: 1005
User: zhouyuyang
Language: C++
Result: Accepted
Time:40 ms
Memory:1348 kb
****************************************************************/
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<queue>
#define ll long long
ll mo=1000000000;
using namespace std;
struct bignum{
ll a[405];
bignum operator * (const bignum b) const{
bignum c;
memset(c.a,0,sizeof(c.a));
for (int i=1;i<=a[0];i++){
ll x=0;
for (int j=1;j<=b.a[0];j++){
x+=a[i]*b.a[j]+c.a[i+j-1];
c.a[i+j-1]=x%mo;
x/=mo;
}
c.a[i+b.a[0]]=x;
}
c.a[0]=a[0]+b.a[0];
while (!c.a[c.a[0]]&&c.a[0]) c.a[0]--;
return c;
}
}b,c,ans;
int a[1005],d[1005],n,m,s;
inline void chai(int x,int y){
for (int i=2;i*i<=x;i++)
while (x%i==0){
x/=i;
a[i]+=y;
}
if (x!=1) a[x]+=y;
}//质因数分解
void qp(int t){
if (t==1) return;
qp(t/2);
c=c*c;
if (t%2) c=c*b;
}//快速次幂
void print(bignum b){
printf("%lld",b.a[b.a[0]]);
for (int i=b.a[0]-1;i;i--) printf("%09lld",b.a[i]);
}
int main(){
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d",&d[i]);
if (d[i]!=-1) s+=d[i]-1; else m++;
}
for (int i=2;i<=n-2;i++) chai(i,1);
chai(m,n-2-s);
for (int i=2;i<=n-2-s;i++) chai(i,-1);
for (int i=1;i<=n;i++)
if (d[i]!=-1)
for (int j=2;j<d[i];j++) chai(j,-1);
ans.a[0]=ans.a[1]=1;
for (int i=1;i<=n;i++)
if (a[i]>0){
c.a[0]=b.a[0]=1;
c.a[1]=b.a[1]=i;
qp(a[i]);
ans=ans*c;
}
print(ans);
}