首页 > 其他分享 >*【学习笔记】(21) Prufer 序列

*【学习笔记】(21) Prufer 序列

时间:2023-09-03 15:33:20浏览次数:37  
标签:ch 21 int 笔记 编号 序列 Prufer 节点

Prufer 序列

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

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

对树构造 Prufer 序列

\(Prufer\) 序列 是这样构造的:

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

过程

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

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

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

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

循环上述操作$n-2 $ 次,就完成了序列的构造。接下来考虑算法的正确性。

\(p\) 是当前编号最小的叶结点,若删除 \(p\) 后未产生叶结点,我们就只能去寻找下一个叶结点;若产生了叶结点 \(x\):

  • 如果 \(x > p\),则反正 \(p\) 往后扫描都会扫到它,于是不做操作;
  • 如果 \(x < p\),因为 \(p\) 原本就是编号最小的,而 \(x\) 比 \(p\) 还小,所以 \(x\) 就是当前编号最小的叶结点,优先删除。删除 继续这样的考虑直到没有更小的叶结点。

算法复杂度分析,发现每条边最多被访问一次(在删度数的时侯),而指针最多遍历每个结点一次,因此复杂度是 \(O(n)\) 的。

具体实现

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

用 Prufer 序列构造树

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

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

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

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

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

具体实现

void solve2(){
	for(int i=1;i<n-1;++i) p[i]=read(),++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&&!(--d[p[i]])&&p[i]<j) f[p[i]]=p[i+1],++i;
	}
}

通过这些过程其实可以理解,\(Prufer\) 序列与带标号无根树建立了双射关系。

prufer 序列的一些性质

1.与一棵有标号的无根树对应

2.某编号结点的入度为该点在prufer 序列中出现的次数 \(+1\)

3.点数为 \(n\) 的有标号无根树个数为 \(n^{n-2}\)
,有根树当然就 \(\times n\) 就行啦。

4.每个点度数为 \(d_i\)
的有标号无根树个数为 \(\frac{(n-2)!}{\prod\limits_{i=1}^n(d_i-1)!}\)

P6086 【模板】Prufer 序列

#include<bits/stdc++.h>
#define N 5000005 
#define int long long 
using namespace std;
int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int n,m,ans;
int f[N],p[N],d[N];
void solve1(){
	for(int i=1;i<n;++i) f[i]=read(),++d[f[i]];
	for(int i=1,j=1;i<=n-2;++i,++j){
		while(d[j]) ++j;p[i]=f[j];
		while(i<=n-2&&!(--d[p[i]])&&p[i]<j) p[i+1]=f[p[i]],++i;
	}
	for(int i=1;i<=n-2;++i) ans^=i*p[i];
}
void solve2(){
	for(int i=1;i<n-1;++i) p[i]=read(),++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&&!(--d[p[i]])&&p[i]<j) f[p[i]]=p[i+1],++i;
	}
	for(int i=1;i<n;++i) ans^=i*f[i];
}
signed main(){
	n=read(),m=read();
	m==1?solve1():solve2();
	printf("%lld\n",ans);
 	return 0;
}

P2290 [HNOI2004]树的计数

#include<bits/stdc++.h>
#define N 205
#define int long long 
using namespace std;
int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
int n,ans=1,sum;
int d[N],c[N][N];
signed main(){
	n=read();
	for(int i=1;i<=n;++i) d[i]=read();
	if(n==1){
		if(d[1]==0) printf("1\n");
		else printf("0\n");
		return 0;
	}
	for(int i=0;i<=n;++i){
		c[i][0]=1;
		for(int j=1;j<=i;++j) c[i][j]=c[i-1][j]+c[i-1][j-1]; 
	} 
	for(int i=1;i<=n;++i){
		if(!d[i]) return printf("0\n"),0;
		d[i]--;
		sum+=d[i];
	}
	if(sum!=n-2) return printf("0\n"),0;
	sum=0;
	for(int i=1;i<=n;++i) ans*=c[n-2-sum][d[i]],sum+=d[i];
	printf("%lld\n",ans); 
 	return 0;
}

Cayley 公式 (Cayley's formula)

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

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

图连通方案数

标签:ch,21,int,笔记,编号,序列,Prufer,节点
From: https://www.cnblogs.com/jiangchen4122/p/17464604.html

相关文章

  • 《C++并发编程实战》读书笔记(2):线程间共享数据
    1、使用互斥量在C++中,我们通过构造std::mutex的实例来创建互斥量,调用成员函数lock()对其加锁,调用unlock()解锁。但通常更推荐的做法是使用标准库提供的类模板std::lock_guard<>,它针对互斥量实现了RAII手法:在构造时给互斥量加锁,析构时解锁。两个类都在头文件<mutex>里声明。std::......
  • 代码随想录算法训练营第二十五天| 216.组合总和III 17.电话号码的字母组合
     216.组合总和III    卡哥建议:如果把 组合问题理解了,本题就容易一些了。    题目链接/文章讲解:https://programmercarl.com/0216.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CIII.html   视频讲解:https://www.bilibili.com/video/BV1wg411873x  做题思路:......
  • celery笔记
    celery介绍1.它是什么?分布式的异步任务框架直译为:芹菜[/ˈseləri]2.可以做什么?异步任务。(异步执行函数)延迟任务。(延迟5s任务(函数))定时任务。(例如:每天23点触发测试)[如果单纯执行定时任务,没必要用celery]3.平台问题celeryisaprojectwithminimal......
  • openGauss学习笔记-59 openGauss 数据库管理-相关概念介绍
    openGauss学习笔记-59openGauss数据库管理-相关概念介绍59.1数据库数据库用于管理各类数据对象,与其他数据库隔离。创建数据对象时可以指定对应的表空间,如果不指定相应的表空间,相关的对象会默认保存在PG_DEFAULT空间中。数据库管理的对象可分布在多个表空间上。59.2表空间在......
  • 【9月摸鱼计划】英特尔酷睿i3-2120与显卡
    酷睿i32120配什么显卡酷睿i3-2120是一款比较老旧的处理器,属于Intel第二代酷睿系列,其使用的主板接口为LGA1155,最大支持DDR3内存。根据其性能水平,推荐搭配中低档次的显卡,以达到较好的性价比。以下是几款适合搭配酷睿i3-2120的显卡:GTX1050Ti:这款显卡是中低端显卡中的佼佼者,搭配i3-212......
  • 分布式服务的接口幂等性如何设计 笔记
    幂等:多次调用方法或者接口不会改变业务状态,可以保证重复调用的结果和单次调用的结果一致。需要幂等场景:用户重复点击(网络波动)MQ消息重复应用使用失败或超时重试机制1.数据库唯一索引(新增)不建议使用2.token+redis(新增、修改)3.分布式锁(新增、修改)快速失败(抢不到锁的线程)控制......
  • [SWPUCTF 2021 新生赛]sql
    [SWPUCTF2021新生赛]sql题目来源:nssctf题目类型:web涉及考点:SQL注入1.又是熟悉的杰哥,先尝试判断闭合类型、回显列数判断闭合类型:/?wllm=1:/?wllm=1':/?wllm=1'%23:%23是#的url编码判断得到闭合类型为单引号闭合判断回显列数:/?wllm=1'groupby3%23:这里是......
  • 魔鬼冲刺学习笔记
    \[\huge{\textbf{魔鬼冲刺}\quad\textbf{2023.8.31-?}}\]高二是大部分OIer的最后一段竞赛时光,这真是“\(One\Last\Olympiad\)”了。所以我们开始魔鬼冲刺了!这里就用来记录这段时期的一些收获,还有学到的知识。由于停课后学习笔记给人的感觉略显凌乱,故在本文中笔者简......
  • 《C和指针》学习笔记
    C和指针学习笔记前置条件1.1配置环境下载vscode安装编译器:这里以MinGw-w64为例。下载MinGw-w64的安装包并解压。添加到系统环境编辑tasks.json(该文件负责项目的编译,如果需要同时编译多个文件,需要对该文件进行如下注释内的修改):{"tasks":[{......
  • 折半搜索 学习笔记
    关于算法折半搜索,又称meetinthemiddle算法。顾名思义,就是将整个搜索的过程分成两个部分分别进行搜索,然后再将两个部分搜索出来的答案进行合并,得到最终的答案。dfs搜索算法一般都是指数级别的,那么我们假如每次dfs时都有两种决策,那么我们执行dfs算法的时间复杂度为\(O......