首页 > 其他分享 >【知识】Prufer 编码

【知识】Prufer 编码

时间:2024-12-01 18:44:41浏览次数:6  
标签:Pr 编码 知识 度数 fer 序列 Prufer 节点

Prüfer 序列

Prufer 序列可以将一个带标号 \(n\) 个节点的树用 \([1, n]\) 中的 \(n-2\) 个整数表示,即 \(n\) 个点的完全图的生成树与长度为 \(n-2\),值域为 \([1, n]\) 的数列构成的双射。

Prufer 序列可以方便地解决一类树相关的计数问题,比如 凯莱定理:\(n^{n-2}\) 个点的完全图的生成树有 \(n^{n-2}\) 个。

对树构造 Prüfer 序列

Prufer 是这样构造的:

每次选择一个编号最小的叶节点并删掉它,然后在序列中记录下它连接到的那个节点。

重复 \(n-2\) 次后就只剩下两个节点,算法结束。

  • \(\mathcal{O}(n \log n)\) 显然,使用堆可以做到 \(\mathcal{O}(n \log n)\) 的复杂度。

  • \(\mathcal{O}(n)\) 使用一个指针代替堆找最小值,可以做到 \(\mathcal{O}(n)\) 的复杂度。

具体而言,指针指向编号最小的叶节点。每次删掉它之后,如果产生了新的叶节点且编号比指针指向的更小,则直接继续删掉,否则自增找到下一个编号最小的叶节点。

Prufer 序列的性质

从上述构造 Prüfer 序列的过程可以看出 Prüfer 序列具有以下两个性质:

  1. 在构造完 Prüfer 序列后,原树中会剩下两个节点,其中一个一定是编号最大的点 \(n\)。
  2. 每个节点在序列中出现的次数是其度数减 \(1\),因此没有出现的就是叶节点。

用 Prufer 序列构造树

根据 Prufer 序列的性质,我们可以得到原树上每个点的度数。

每次我们选择一个编号最小的度数为 \(1\) 的节点,与当前枚举到的 Prufer 序列的点连接,然后同时减掉两个点的度数。重复 \(n-2\) 次后就只剩下两个度数为 \(1\) 的节点,其中一个是 \(n\),把它们连接起来即可。

  • \(\mathcal{O}(n \log n)\) 同样地,显然,使用堆可以做到 \(\mathcal{O}(n \log n)\) 的复杂度。

  • \(\mathcal{O}(n)\) 类似地,使用一个指针代替堆找最小值,可以做到 \(\mathcal{O}(n)\) 的复杂度。

具体而言,指针指向编号最小的度数为 \(1\) 的节点。每次将它与当前枚举到的 Prufer 序列的点连接之后,如果产生了新的度数为 \(1\) 的节点且编号比指针指向的更小,则直接继续将它与下一个 Prüfer 序列的点连接,否则自增找到下一个编号最小的度数为 \(1\) 的节点。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

int n, m;
int f[N], d[N], p[N];

void tree2prufer()
{
    for (int i = 1; i < n; i ++ )
    {
        scanf("%d", &f[i]);
        d[f[i]] ++ ;
    }

    for (int i = 0, j = 1; i < n - 2; j ++ )
    {
        while (d[j]) j ++ ;
        p[i ++ ] = f[j];
        while (i < n - 2 && -- d[p[i - 1]] == 0 && p[i - 1] < j) p[i ++ ] = f[p[i - 1]];
    }

    for (int i = 0; i < n - 2; i ++ ) printf("%d ", p[i]);
}

void prufer2tree()
{
    for (int i = 1; i <= n - 2; i ++ )
    {
        scanf("%d", &p[i]);
        d[p[i]] ++ ;
    }
    p[n - 1] = n;

    for (int i = 1, j = 1; i < n; i ++, j ++ )
    {
        while (d[j]) j ++ ;
        f[j] = p[i];
        while (i < n - 1 && -- d[p[i]] == 0 && p[i] < j) f[p[i]] = p[i + 1], i ++ ;
    }

    for (int i = 1; i <= n - 1; i ++ ) printf("%d ", f[i]);
}

int main()
{
    scanf("%d%d", &n, &m);
    if (m == 1) tree2prufer();
    else prufer2tree();

    return 0;
}

Cayley 公式 (Cayley's formula)

完全图 \(K_n\) 有 \(n^{n-2}\) 棵生成树。

怎么证明?方法很多,但是用 Prüfer 序列证是很简单的。任意一个长度为 \(n-2\) 的值域 \([1,n]\) 的整数序列都可以通过 Prüfer 序列双射对应一个生成树,于是方案数就是 \(n^{n-2}\)。

图连通方案数

Prüfer 序列可能比你想得还强大。它能创造比 凯莱公式 更通用的公式。比如以下问题:

一个 \(n\) 个点 \(m\) 条边的带标号无向图有 \(k\) 个连通块。我们希望添加 \(k-1\) 条边使得整个图连通。求方案数。

证明

设 \(s_i\) 表示每个连通块的数量。我们对 \(k\) 个连通块构造 Prüfer 序列,然后你发现这并不是普通的 Prüfer 序列。因为每个连通块的连接方法很多。不能直接淦就设啊。于是设 \(d_i\) 为第 \(i\) 个连通块的度数。由于度数之和是边数的两倍,于是 \(\sum_{i=1}^kd_i=2k-2\)。则对于给定的 \(d\) 序列构造 Prüfer 序列的方案数是

\[\binom{k-2}{d_1-1,d_2-1,\cdots,d_k-1}=\frac{(k-2)!}{(d_1-1)!(d_2-1)!\cdots(d_k-1)!} \]

对于第 \(i\) 个连通块,它的连接方式有 \({s_i}^{d_i}\) 种,因此对于给定 \(d\) 序列使图连通的方案数是

\[\binom{k-2}{d_1-1,d_2-1,\cdots,d_k-1}\cdot \prod_{i=1}^k{s_i}^{d_i} \]

现在我们要枚举 \(d\) 序列,式子变成

\[\sum_{d_i\ge 1,\sum_{i=1}^kd_i=2k-2}\binom{k-2}{d_1-1,d_2-1,\cdots,d_k-1}\cdot \prod_{i=1}^k{s_i}^{d_i} \]

好的这是一个非常不喜闻乐见的式子。但是别慌!我们有多元二项式定理:

\[(x_1 + \dots + x_m)^p = \sum_{\substack{c_i \ge 0 ,\ \sum_{i=1}^m c_i = p}} \binom{p}{c_1, c_2, \cdots ,c_m}\cdot \prod_{i=1}^m{x_i}^{c_i} \]

那么我们对原式做一下换元,设 \(e_i=d_i-1\),显然 \(\sum_{i=1}^ke_i=k-2\),于是原式变成

\[\sum_{e_i\ge 0,\sum_{i=1}^ke_i=k-2}\binom{k-2}{e_1,e_2,\cdots,e_k}\cdot \prod_{i=1}^k{s_i}^{e_i+1} \]

化简得到

\[(s_1+s_2+\cdots+s_k)^{k-2}\cdot \prod_{i=1}^ks_i \]

\[n^{k-2}\cdot\prod_{i=1}^ks_i \]

证毕!

标签:Pr,编码,知识,度数,fer,序列,Prufer,节点
From: https://www.cnblogs.com/fanrunze/p/18580160

相关文章

  • 【知识】图论 朱刘算法梳理
    朱刘算法:树形图的定义:以某一个点为根的有向树,被称为树形图一个有向图,满足无环且每个点的入度为\(1\)(除了根节点),被称为树形图最小树形图:对于所有树形图中,找到一个总权值和最小的树形图,被称为最小树形图。最小树形图问题本质上其实就是有向图上的最小生成树问题。......
  • 网络基础知识:交换机和路由器的工作原理,零基础入门到精通,收藏这一篇就够了
    交换机和路由器是网络核心设备,分别工作在数据链路层(第2层)和网络层(第3层)。交换机通过MAC地址学习和转发数据帧,支持VLAN划分广播域;路由器使用IP地址和路由表选择最佳路径转发数据包。交换机适用于局域网内高速转发,路由器连接不同网络。交换机和路由器是网络中的关键设备,分别......
  • C语言循环与详解操作符 基础知识大汇总(下)(保驾护航大家的C语言)(保姆级超详细解说)(应对各
    hello大家好啊,这里是星空没有雨,今天你的城市下雨了吗,今天星宇给大家带来c语言环以及操作符详解,程让我们更多的新手伙伴们更好的入门   OK,now,let'sgo1.详解操作符/与%(1)/运算符/⽤来完成除法。除号的两端如果是整数,执⾏的是整数除法,得到的结果也是整数。......
  • 【NLP高频面题 - LLM架构篇】旋转位置编码RoPE如何进行外推?
    【NLP高频面题-LLM架构篇】旋转位置编码RoPE如何进行外推?重要性:★★★......
  • 小知识点
    1.位运算乘除x=x*(2^n)可以转化成x<<nx=x/(2^n)可以转化成x>>n2.Forfor(registerinti(1);i<=n;i++)3.短函数前加inline4.判断奇偶if(a%2==0)可以转化成if((a&1)==0)5.取模用&x=667%4;可以转化成x=667&(4-1);x=667%32;......
  • 1201-字符串编码
    最小栈leetcode394.题目大意:[]前的数字为出现的次数,中的内容会要重复的数据,例如输入:s="3[a2[c]]"输出:"accaccacc"解题思路:主要难点为嵌套中括号,利用栈的特点设计两个LinkedList存储次数和重复值,每次遇到左括号的时候将当前的数字和重复值分别入栈,遇到右括号的时候将数......
  • Java基础知识-第4章-认识Java中的数组
    【导航】1、数组概述Java中的数组可以认为是一种容器,其可以同时存放多个数据值(元素)。Java语言中提供的数组是用来存储固定大小的同类型元素。数组的特点:数组是一种引用数据类型,但是数组元素类型不限。数组当中的多个数据类型必须统一数组的长度一旦确定,在程序运行......
  • CISSP错题或模糊知识(未整理)
    我超怕的-https://www.cnblogs.com/iAmSoScArEd/p/18579786具有公共IP地址的数据包通常会被允许进入网络,因此您不应创建规则来阻止它们。具有内部源地址的数据包不应来自网络外部,因此应阻止它们进入网络。具有外部源地址的数据包永远不会在内部网络上找到,因此应阻止它们离开......
  • day01(Linux底层)基础知识
    目录导学基础知识1、Bootloader是什么2、Bootloader的基本作用3、入式中常见的Bootloader有哪些4、Linux系统移植为什么要使用bootloader5、uboot和Bootloader之间的关系6.Uboot的获取7、uboot版本命名8、uboot版本选择9、uboot的特点10.Uboot使用导学移植......
  • UI自动化基础知识
    一、UI自动化测试介绍1、什么是自动化测试概念:由程序代替人工进行系统校验的过程1.1自动化测试能解决的问题?回归测试(冒烟测试)针对之前老的功能进行测试,通过自动化的代码来实现。针对上一个版本的问题的回归兼容性测试:web实例化不同的浏览器驱动相当于对不同的浏览器进行操作......