prufer序列
$prufer$ 序列
突然想起来自己还学过这么一个东西...
定义
长度为 $n-2$ 的 $prufer$ 序列可以和 $n$ 个节点的有标号无根树形成一一映射;
构建方法
每次删去当前编号最小的叶子 $x$,并在序列末尾加入与 $x$ 相连的点的编号;
还原方法
1.找出1至n中没有在序列中出现的最小数 $x$。连接 $x$ 与序列首项,以后找最小值时不再考虑$x$,同时删去序列的首项。
2.重复上述步骤,直到序列用尽;
3.连接那两个没有作为最小值出现过的点;
一些性质
1.对于一个度数为$x$的点,要删去$x-1$个它的相邻点后它才会被删去,所以它在prufer序列中出现的次数就是 $x-1$;
2.n个点的带标号无根树一共有$n^{n-2}$种;
3.对于有根树,在无根树的基础上再枚举一个根就好了,所以共有 $n^{n-1}$ 种;
几个例题
树的计数:https://www.luogu.org/problem/P2290
题意概述:已知每个点的度数,求有多少种满足条件的树;
根据性质1,已经知道了每个点在prufer序列中的出现次数,接下来只要求一个可重排列就好了。
$\frac{(n-2)!}{\prod(d_i-1)!}$
1 # include <cstdio> 2 # include <iostream> 3 # define R register int 4 5 using namespace std; 6 7 const int N=160; 8 int n,s; 9 int d[N]; 10 int v[N]; 11 12 void add (int x,int a) 13 { 14 for (R i=2;i<=x;++i) 15 while(x%i==0) v[i]+=a,x/=i; 16 } 17 18 int main() 19 { 20 scanf("%d",&n); 21 for (R i=1;i<=n;++i) 22 { 23 scanf("%d",&d[i]); 24 if(n!=1&&d[i]==0) 25 { 26 printf("0"); 27 return 0; 28 } 29 s+=d[i]; 30 } 31 if(s!=2*(n-1)) { printf("0"); return 0; } 32 for (R i=1;i<=n-2;++i) add(i,1); 33 for (R i=1;i<=n;++i) 34 for (R j=1;j<=d[i]-1;++j) 35 add(j,-1); 36 long long ans=1; 37 for (R i=1;i<=n;++i) 38 for (R j=1;j<=v[i];++j) 39 ans=ans*i; 40 printf("%lld",ans); 41 return 0; 42 }
明明的烦恼:https://www.luogu.org/problem/P2624
题意概述:有的点知道了度数,有的点不清楚,求有多少种满足条件的树;
设有 $k$ 个点知道了度数,$n-k$个点不知道, $s$ 为所有已知度数的点出现的次数和。首先在 $n-2$ 个位置中找出 $s$ 个位置来填这些数,未确定的数可以随便填,所以答案就是:
$\binom{n-2}{s}\frac{s!}{\prod(d_i-1)!}(n-k)^{n-2-s}$
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # define R register int 5 6 using namespace std; 7 8 const int N=1005; 9 int n,s,cnt; 10 int v[N],d[N]; 11 int ans[100*N]; 12 13 void add (int x,int a) 14 { 15 for (R i=2;i<=x;++i) 16 while(x%i==0) x/=i,v[i]+=a; 17 } 18 19 void write() 20 { 21 for (R i=ans[0];i;i--) 22 printf("%d",ans[i]); 23 } 24 25 26 void mul (int x) 27 { 28 for (R i=1;i<=ans[0];++i) ans[i]*=x; 29 for (R i=1;i<=ans[0]+5;++i) 30 { 31 ans[i+1]+=ans[i]/10; 32 ans[i]%=10; 33 } 34 ans[0]+=5; 35 while(ans[ ans[0] ]==0&&ans[0]) ans[0]--; 36 } 37 38 int main() 39 { 40 scanf("%d",&n); 41 for (R i=1;i<=n;++i) 42 { 43 scanf("%d",&d[i]); 44 if(n==1) 45 { 46 if(d[1]==0) { printf("1"); return 0; } 47 else { printf("0"); return 0; } 48 } 49 if(d[i]==0&&n!=1) { printf("0"); return 0; } 50 if(d[i]!=-1) s+=d[i]; 51 else cnt++; 52 } 53 if(cnt+s>2*(n-1)) { printf("0"); return 0; } 54 s=0; for (R i=1;i<=n;++i) if(d[i]!=-1) s+=d[i]-1; 55 for (R i=2;i<=n-2;++i) add(i,1); 56 for (R i=2;i<=n-2-s;++i) add(i,-1); 57 for (R i=1;i<=n;++i) 58 { 59 if(d[i]==-1) continue; 60 for (R j=2;j<=d[i]-1;++j) add(j,-1); 61 } 62 ans[0]=1; ans[1]=1; 63 for (R i=2;i<=n;++i) 64 for (R j=1;j<=v[i];++j) 65 mul(i); 66 for (R i=1;i<=n-2-s;++i) 67 mul(cnt); 68 write(); 69 return 0; 70 }
数树:https://www.luogu.org/problem/P5206
先咕着,我就不信NOIP考这题。
---shzr