BZOJ1005: [HNOI2008]明明的烦恼

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;
}

点赞