bzoj 1005 [HNOI2008] 明明的烦恼 题解

转载请注明出处:http://blog.csdn.net/jiangshibiao/article/details/22651081

【原题】

1005: [HNOI2008]明明的烦恼

Time Limit: 1 Sec   Memory Limit: 162 MB
Submit: 1963   Solved: 768
[ Submit][ Status]

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


【分析】开始觉得无从下手啊。在SYC1999iGzhou的指导下,先了解了一种神奇的东西,叫做:prufer序列(无奈有这么多的公式、定理,以前怎么没有听说过呢)。

介绍一下:

一种生成Prufer序列的方法是迭代删点,直到原图仅剩两个点。对于一棵顶点已经经过编号的树T,顶点的编号为{1,2,...,n},在第i步时,移去所有叶子节点(度为1的顶点)中标号最小的顶点和相连的边,并把与它相邻的点的编号加入Prufer序列中,重复以上步骤直到原图仅剩2个顶点。以右边的树为例子,首先在所有叶子节点中编号最小的点是2,和它相邻的点的编号是3,将3加入序列并删除编号为2的点。接下来删除的点是4,5被加入序列,然后删除5,1,此时原图仅剩两个点,Prufer序列构建完成,为{3,5,1,3}

bzoj 1005 [HNOI2008] 明明的烦恼 题解_第1张图片

这其实就是对于一棵树的编码罢了。这有什么用呢?有一个定理是这样的:prufer编码是可以和无根树之间形成一一对应关系。这样子,计算树的个数,就转化成了计算序列的个数了。注意:题目里每个点的要求个数要减1后计算排列的个数,因为会有父节点。

【思考】怎么计算呢?就用排列组合的原理。我们先考虑不是-1的点。一共有N-2个格子,设当前的点要求为a1(已经减1了),我们可以放的总数是:C(N-2,a1)。这样占了a1个格子。下一个a2的情况就是C(N-2-a1,a2)。依次类推。

然后是-1的情况,我想了很长时间。

原来是这样想的:设最后还有K个格子,且有SUM个-1。我们用DP思路。f[i][j]表示放到最后K个格子中的第i个格子时,放第j种颜色(注意颜色是不下降的,最后只需乘上阶乘即可)的情况数。但是因为要用高精,DP会很麻烦。

感谢HHD大牛的指导:其实就是SUM^K。为什么?对于每一个格子,我们都可以放SUM种情况~~~~。

【优化】为了不超时,用质数表来优化。

【代码】

#include<cstdio>
using namespace std;
const int size=168;
const int data[size+1]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,
79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,
193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,
313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,
443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,
587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,
719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857
,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997};
int n,len,i,a,sum,j,k;
int t[10001],ans[size+1];
void C(int n,int m)
{
  int i,p,j;
  for (i=m+1;i<=n;i++)
  {
    p=i;j=1;
    while (p>1){while (p%data[j]==0) {ans[j]++;p/=data[j];}j++;}
  }
  for (i=1;i<=n-m;i++)
  {
    p=i;j=1;
    while (p>1){while (p%data[j]==0) {ans[j]--;p/=data[j];}j++;}
  }
}
int main()
{
  scanf("%d",&n);len=n-2;
  for (i=1;i<=n;i++)
  {
    scanf("%d",&a);
    if (a==0||len-(a-1)<0) {printf("0");return 0;}
    if (a==-1) {sum++;continue;}a--;
    C(len,a);len-=a;
  }
  if (len)
  {
    j=1;
    while (sum>1){while (sum%data[j]==0) {ans[j]+=len;sum/=data[j];} j++;}
  }
  t[1]=1;k=1;
  for (i=1;i<=size;i++)
    while (ans[i])
    {
      ans[i]--;
      for (j=1;j<=k;j++) t[j]*=data[i];
      for (j=1;j<=k;j++) if (t[j]>9) {t[j+1]+=t[j]/10;t[j]%=10;}
      while (t[k+1]) {k++;t[k+1]+=t[k]/10;t[k]%=10;}
    }
  for (i=k;i>0;i--)printf("%d",t[i]);
  return 0;
}

你可能感兴趣的:(题解,数学,树,bzoj)