无根树(无环连通无向图)的Prufer编码

Prufer 数列,可以用来解一些关于无根树计数的问题。
Prufer 数列是一种无根树的编码表示,对于一棵 n n 个节点带编号的无根树,对应唯一一串长度为 n1 的 Prufer 编码。
(1)无根树转化为 Prufer 序列。
首先定义无根树中度数为 1 1 的节点是叶子节点。
找到编号最小的叶子并删除,序列中添加与之相连的节点编号,重复执行直到只剩下 2 个节点。
如下图的树对应的 Prufer P r u f e r 序列就是3,5,1,3。
无根树
具体实现可以用一个 set 搞定,维护度数为 1 1 的节点。复杂度 O(nlogn)
(2)Prufer序列转化为无根树。
设点集 V={1,2,3,...,n} V = { 1 , 2 , 3 , . . . , n } ,每次取出 Prufer 序列中最前面的元素u,在V中找到编号最小的没有在 Prufer 序列中出现的元素 v v ,给 u v v 连边然后分别删除,最后在 V 中剩下两个节点,给它们连边。最终得到的就是无根树。
具体实现也可以用一个 set,维护 Prufer 序列中没有出现的编号。复杂度 O(nlogn) O ( n l o g n )
最后有一个很重要的性质就是* Prufer 序列中某个编号出现的次数就等于这个编号的节点在无根树中的度数-1*

而这个编码的应用就是对无根树进行计数。一个 n n 个节点的无根树唯一地对应了一个长度为 n2 的编码数列,数列中的每个数都在 1 1 n 的范围内。
1)我们可以直接推出 n n 个点的无向完全图的生成树的计数:n(n2) n n 个点的有标号无根树的计数。

2)一个有趣的推广是,n 个节点的度依次为 D1,D2,,Dn D 1 , D 2 , … , D n 的无根树共有 (n2)!(D11)!(D21)!...(Dn1)! ( n − 2 ) ! ( D 1 − 1 ) ! ( D 2 − 1 ) ! . . . ( D n − 1 ) ! 个,因为此时 Prüfer 编码中的数字 i i 恰好出现 Di1 次。

n n 种元素,共 n2 个,其中第 i i 种元素有 Di1 个,求排列数。

3) n n 个节点的度依次为 D1,D2,,Dn,另外有 m m 个节点度数未知,求有多少种生成树?(BZOJ1005 明明的烦恼)

令每个已知度数的节点的度数为 di,有 n n 个节点,m 个节点未知度数, left=(n2)(d11)(d21)...(dnm1) l e f t = ( n − 2 ) − ( d 1 − 1 ) − ( d 2 − 1 ) − . . . − ( d n − m − 1 )

已知度数的节点可能的组合方式如下

(n2)!(d11)!(d21)!...(dnm1)!left! ( n − 2 ) ! ( d 1 − 1 ) ! ( d 2 − 1 ) ! . . . ( d n − m − 1 ) ! l e f t !

剩余 left l e f t 个位置由未知度数的节点随意填补,方案数为 mleft m l e f t
于是最后有

ans=(n2)!mleft(d11)!(d21)!...(dnm1)!left! a n s = ( n − 2 ) ! m l e f t ( d 1 − 1 ) ! ( d 2 − 1 ) ! . . . ( d n − m − 1 ) ! l e f t !

BZOJ1211

一个有 n n 个结点的树,设它的结点分别为 v1,v2,,vn,已知第 i i 个结点vi的度数为 di,问满足这样的条件的不同的树有多少棵。给定 n n d1,d2,,dn,编程需要输出满足 d(vi)=di d ( v i ) = d i 的树的个数。

import java.util.*;
import java.math.*;
public class BZOJ1211 {
    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        int n = cin.nextInt();
        int[] vec = new int[n];
        int tot = 0;
        for (int i = 0; i < n; ++i) {
            vec[i] = cin.nextInt();
            tot += vec[i];
            if (vec[i] == 0 && n > 1) {
                System.out.println(0);
                return;
            }
        }
        if (tot != (n-1)*2) {
            System.out.println(0);
            return;
        }
        if (n == 1 && vec[0] > 0) {
            System.out.println(0);
        } else {
            BigInteger up = Fact(n-2);
            BigInteger down = new BigInteger("1");
            for (int i = 0; i < n; ++i) {
                down.multiply(Fact(vec[i]-1));
            }
            System.out.println(up.divide(down));
        }
    }
    public static BigInteger Fact(int n) {
        BigInteger result = new BigInteger("1");
        for (int i = 1; i <= n; ++i) {
            result = result.multiply(BigInteger.valueOf(i));
        }
        return result;
    }
}
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值