给定n个互不相同的顶点,求它们可以构成的不相同的无向连通图数量(POJ-1737)

1、题目

给定n个互不相同的顶点,求它们可以构成的不相同的无向连通图数量
http://poj.org/problem?id=1737

2、算法思路

引文思路描述对我来说有点抽象,这里尝试再说细一些(引文列表见本文文末)

思路一:直接求连通图的方案数(本文代码)

递推公式:F(n) = Sum{ F(k)∗F(n-k)∗C(n-2,k-1)∗(2^k -1) | 1<=k

(1)将整个连通图划分为两部分

首先,考虑把n个节点构成的整个连通图分为A和B两块,若连通块A包含k个节点(k个节点含节点2,k的取值从1至n-1),则另外一个连通块B包含n-k个节点(n-k个节点含节点1)
A、B两部分各自的连通图方案数分别为F(A)和F(B),则组合起来的方案数有:
F(k)*F(n-k)
连通块A的取法:A部分除去节点2的k-1个节点从所有n个节点(除去节点1和节点2)中选取,因此有:
C(n-2,k-1)

TODO:这里为什么是C(n-2,k-1),而不是C(n,k)?
(2)将两个部分连接起来

接着,由于整体上要求由n个节点构成的图是连通的,因此要设法将上述的连通块A和连通块B连接起来。如图,从节点1到A部分的k个节点的k个连接一共有2^k种排列组合的连接方式,需要排除全不连接的情况,因此满足条件的有:
2^k-1种
给定n个互不相同的顶点,求它们可以构成的不相同的无向连通图数量(POJ-1737)_第1张图片
综合起来,得到思路一的递推公式:
F(n) = Sum{ F(k)∗F(n-k)∗C(n-2,k-1)∗(2^k -1) | 1<=k

思路二:将总的方案数减掉所有不连通图的方案数

(1)总的方案数

n个节点的全连通图(即任意两点间都有边)的连接数为C(n,2);考虑每个边的存在和不存在2种情况,因此,总的方案数为:
2^(C(n,2))

(2)非连通图的方案数

将整个节点集合考虑为2个部分:包括节点1的连通部分A(含k个节点);以及不包括节点1的其它部分B(含n-k个节点)
给定n个互不相同的顶点,求它们可以构成的不相同的无向连通图数量(POJ-1737)_第2张图片

a.连通部分A的考虑

首先,划分出包括k个节点连通部分A:A中除去点1,其余k-1个节点从剩余n个节点中取,取法数为:
C(n-1,k-1)
A为连通区域,连通图方案数为:
F(k)
因此,连通区域A的方案数为:
C(n-1,k-1)∗F(k)

b.其它部分B的考虑

B中的n-k的各点间任意连边即可,方案数为:
2^(C(n-k,2))

综合起来,得到思路二的递推公式:
F(n)= 2^(C(n,2))-Sum{ C(n-1,k-1) * F(k) * 2^(C(n-k,2)) | 0<=k

4、代码实现

(Java代码)

import java.util.Scanner;
import java.math.BigInteger;

public class Main {

    private static BigInteger[] _f = new BigInteger[100];

    private static BigInteger E(int base, int n) {
        if (n == 0) {
            return BigInteger.valueOf(1);
        }

        BigInteger result = BigInteger.valueOf(base);
        for (int i=1; i<n; i++) {
            result = result.multiply(BigInteger.valueOf(base));
        }
        return result;
    }

    private static BigInteger factorial(int n) {
        if (n == 0) {
            return BigInteger.valueOf(1);
        }

        BigInteger result = BigInteger.valueOf(1);
        for (int i=1; i<=n; i++) {
            result = result.multiply(BigInteger.valueOf(i));
        }
        return result;
    }

    private static BigInteger C(int n, int m) {
        return factorial(n).divide(factorial(m)).divide(factorial(n-m));
    }

    /* 思路一:直接求连通图方案数
     * 公式:F(n) = Sum{ F(k)∗F(n-k)∗C(n-2,k-1)∗(2^k -1) | 1<=k
    private static BigInteger F(int n) {

        if (_f[n] != null) return _f[n];

        if (n<=2) {
            return BigInteger.valueOf(1);
        }

        BigInteger res = BigInteger.valueOf(0);
        for (int i=1; i<n; i++) {
            BigInteger m1 = F(i).multiply(F(n-i));
            BigInteger m2 = C(n-2, i-1);
            BigInteger m3 = E(2, i).subtract(BigInteger.valueOf(1));
            res = res.add(m1.multiply(m2).multiply(m3));
        }
        
        _f[n] = res;
        return res;
    }

    /* 思路二:总方案数-非联通图方案数
     * 公式:F(n)= 2^(C(n,2))-Sum{ C(n-1,k-1) * F(k) * 2^(C(n-k,2)) | 0<=k
    // private static BigInteger F(int n) {

    //     if (_f[n] != null) return _f[n];
        
    //     BigInteger all = E(2,C(n,2).intValue());

    //     BigInteger except = BigInteger.valueOf(0);
    //     for (int i=1; i
    //         BigInteger m1 = C(n-1, i-1);
    //         BigInteger m2 = F(i);
    //         BigInteger m3 = E(2, C(n-i,2).intValue());
    //         except = except.add(m1.multiply(m2).multiply(m3));
    //     }

    //     BigInteger res = all.subtract(except);

    //     _f[n] = res;
    //     return res;
    // }

    public static void main(String arg[]){
        Scanner s = new Scanner(System.in);
        while(s.hasNextInt()) {
            int n = s.nextInt();
            if (n == 0) break;
            System.out.println(F(n));
        }
        return;

    }

}

4、最后说几个点

(1)引文说明:网上关于这题有很多检索结果,但是不少还是存在谬误(公式错或公式和代码对不上等等);下面几篇文章就正、反两种思路给出了比较详细的分析,也给出了部分代码:https://www.cnblogs.com/longdouhzt/archive/2012/03/05/2380994.html(思路1公式有问题,思路2正确)
https://blog.csdn.net/wu_tongtong/article/details/79569016(思路1公式正确,但是没给代码)
(2)大整数计算:下文Java代码直接使用BigInteger(ref:https://blog.csdn.net/baidu_41560343/article/details/89971950)
*** 此外:下文描述了根据“同余定理”的模1000000007技巧(本文代码未使用该技巧)
https://blog.csdn.net/weixin_41754415/article/details/88998826
(3)计算效率问题:下文Java代码通过对主计算函数f()加入一片“缓存”(数组_f[])来存储f的计算结果的方式避免重复计算。其实类似的思路也可以用在阶乘计算函数factorial()以及幂函数计算函数E()都可以用类似的方式来处理。但是由于在poj已经AC了(213ms),就没有做进一步处理了
(4)OJ的Java代码“套路”(输入、输出的处理等):https://blog.csdn.net/qq_38174756/article/details/83537860
(5)幂指数函数计算有个小“坑”,一定要做幂等于0的返回1的分支… 基础知识…

“送”一张图

横坐标是节点数量,纵坐标是n个节点构成的所有图中连通图的占比,看上去,n为两位数时,各种情形中99%以上都是连通图了
给定n个互不相同的顶点,求它们可以构成的不相同的无向连通图数量(POJ-1737)_第3张图片

你可能感兴趣的:(poj,动态规划,在线笔试)