【BZOJ1005】明明的烦恼(HNOI2008)-Prufer序列+组合计数+高精度
测试地址:明明的烦恼
做法:本题需要用到Prufer序列+组合计数+高精度。
这题就是HNOI2004-树的计数(BZOJ1211)的一个加强版,数据范围增大了,有些点还没有度数限制。但是我们也可以用类似的思路推出答案。我写的BZOJ1211题解看这里。
首先,Prufer序列中某些点的出现次数已经确定了,那么这些点之间的排列数量是一个可重排列,设一共有
稍微化一下式子可得:
然后用和我写的BZOJ1211的题解中类似的处理方法算出最后答案质因数分解的结果,乘起来算出答案即可。由于答案可能很大,所以需要用到高精度计算。最后还要注意判断一下无解,有两种情况是无解的:1.
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,d[2010],tot=0,t=0,a[2010][2010],sum[2010][2010],ans[2010],final[3010]={0},digit;
ll p[2010];
bool prime[2010]={0};
void calc_prime(int limit)
{
for(ll i=2;i<=limit;i++)
{
if (!prime[i]) p[++tot]=i;
for(ll j=1;j<=tot&&i*p[j]<=limit;j++)
{
prime[i*p[j]]=1;
if (i%p[j]==0) break;
}
}
}
void mult(int x)
{
for(int i=1;i<=digit;i++)
final[i]*=x;
for(int i=1;i<=digit;i++)
if (final[i]>9)
{
final[i+1]+=final[i]/10;
final[i]%=10;
if (i==digit) digit++;
}
}
int main()
{
scanf("%d",&n);
calc_prime(2000);
for(int i=2;i<=2000;i++)
{
for(int j=1;j<=tot;j++)
{
sum[i][j]=0;
int x=i;
while(x%p[j]==0) sum[i][j]++,x/=p[j];
a[i][j]=sum[i][j];
sum[i][j]+=sum[i-1][j];
}
}
int s=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&d[i]);
if (d[i]!=-1) s+=d[i]-1,t++;
if (n!=1&&d[i]==0) {printf("0");return 0;}
}
if (s>n-2) printf("0");
else
{
for(int i=1;i<=tot;i++) ans[i]=sum[n-2][i];
for(int i=1;i<=n;i++)
if (d[i]!=-1)
{
for(int j=1;j<=tot;j++)
ans[j]-=sum[d[i]-1][j];
}
for(int i=1;i<=tot;i++)
ans[i]-=sum[n-2-s][i];
for(int i=1;i<=tot;i++)
ans[i]+=(n-2-s)*a[n-t][i];
final[1]=1,digit=1;
for(int i=1;i<=tot;i++)
for(int j=1;j<=ans[i];j++)
mult(p[i]);
bool flag=0;
for(int i=digit;i>=1;i--)
{
if (final[i]) flag=1;
if (flag) printf("%d",final[i]);
}
}
return 0;
}