HDU3070:Counting spanning trees

——problem:有度限制的生成树个数

——solution:Cayley公式,Prüfer编码,递推

Cayley公式是:一个完全图K_nn^(n-2)棵生成树,换句话说n个节点的带标号的无根树有n^(n-2)个。

 

Cayley公式的一个非常简单的证明,证明依赖于Prüfer编码,它是对带标号无根树的一种编码方式:

    给定一棵带标号的无根树,找出编号最小的叶子节点,写下与它相邻的节点的编号,然后删掉这个叶子节点。反复执行这个操作直到只剩两个节点为止。由于节点数n>2的树总存在叶子节点,因此一棵n个节点的无根树唯一地对应了一个长度为n-2的数列,数列中的每个数都在1n的范围内。下面我们只需要说明,任何一个长为n-2、取值范围在1n之间的数列都唯一地对应了一棵n个节点的无根树,这样我们的带标号无根树就和Prüfer编码之间形成一一对应的关系,Cayley公式便不证自明了。

    注意到,如果一个节点A不是叶子节点,那么它至少有两条边;但在上述过程结束后,整个图只剩下一条边,因此节点A的至少一个相邻节点被去掉过,节点A的编号将会在这棵树对应的Prüfer编码中出现。反过来,在Prüfer编码中出现过的数字显然不可能是这棵树(初始时)的叶子。于是我们看到,没有在Prüfer编码中出现过的数字恰好就是这棵树(初始时)的叶子节点。找出没有出现过的数字中最小的那一个(比如④),它就是与Prüfer编码中第一个数所标识的节点(比如③)相邻的叶子。接下来,我们递归地考虑后面n-3位编码(别忘了编码总长是n-2):找出除④以外不在后n-3位编码中的最小的数(左图的例子中是⑦),将它连接到整个编码的第2个数所对应的节点上(例子中还是③)。再接下来,找出除④和⑦以外后n-4位编码中最小的不被包含的数,做同样的处理……依次把③⑧②⑤⑥与编码中第34567位所表示的节点相连。最后,我们还有①和⑨没处理过,直接把它们俩连接起来就行了。由于没处理过的节点数总比剩下的编码长度大2,因此我们总能找到一个最小的没在剩余编码中出现的数,算法总能进行下去。这样,任何一个Prüfer编码都唯一地对应了一棵无根树,有多少个n-2位的Prüfer编码就有多少个带标号的无根树。

 一个有趣的推广是,n个节点的度依次为D1, D2, ..., Dn的无根树共有(n-2)! / [ (D1-1)!(D2-1)!..(Dn-1)! ]个,因为此时Prüfer编码中的数字i恰好出现Di-1次。

 

n^(n-2)是从每个位置考虑过来,每个位置有n种可取,做n-2次这样的操作n^(n-2)。

换个角度思考:

 从每个数考虑过来,即是每个数最多能被d个位置使用。

 这时候就可以得出递推公式:

      dp[i][j]=(dp[i][j]+dp[i-1][j-k]*C(k,n-2-(j-k)));

dp[i][j]表示放完第i个数,用掉j个位置总共有多少种方法。

 

View Code
1 #include<stdio.h>
2 #include<memory.h>
3  #define N 100
4  #define mod 20090829
5  int dp[N][N],c[N][N];
6 int n,d,i,j,k;
7 int min(int a,int b)
8 {
9 return a<b?a:b;
10 }
11 long long gcd(long long a,long long b)
12 {
13 if (b==0) return a;
14 else return gcd(b,a%b);
15 }
16 long long C(long long k,long long n)
17 {
18 long long num[N];
19 int i,j;
20 long long temp,x;
21 if (c[n][k]!=0)
22 return c[n][k];
23 for (i=k+1;i<=n;i++)
24 num[i-k]=i;
25 for (i=2;i<=n-k;i++)
26 {
27 x=i;
28 for (j=k+1;j<=n;j++)
29 {
30 temp=gcd(x,num[j-k]);
31 x/=temp;
32 num[j-k]/=temp;
33 if (x==0) break;
34 }
35 }
36 temp=1;
37 for (i=k+1;i<=n;i++)
38 temp=(temp*num[i-k])%mod;
39 c[n][k]=temp;
40 return temp;
41 }
42 int main()
43 {
44 while (scanf("%d%d",&n,&d)!=EOF)
45 {
46 memset(dp,0,sizeof(dp));
47 dp[0][0]=1;
48 for (i=1;i<=n;i++)
49 for (j=0;j<=n-2;j++)
50 for (k=0;k<=min(j,d-1);k++)
51 dp[i][j]=(dp[i][j]+dp[i-1][j-k]*C(k,n-2-(j-k)))%mod;
52 printf("%lld\n",dp[n][n-2]);
53 }
54 return 0;
55 }

posted on 2011-03-26 21:40  风也轻云也淡  阅读(451)  评论(0编辑  收藏  举报