学习笔记:树与图上的计数问题

Prüfer 序列#

n 个点的有标号无根树可以与一个长度为 n2 的 Prüfer 序列对应。

从树到 Prüfer 序列#

  1. f 为空序列。
  2. 如果当前树上多于两个节点,假设当前标号最小的叶子为 x,与 x 相连的节点标号为 y,那么把 x 从树上删除,把 y 加入 f 末尾。
  3. 重复 2. 直到树上只有两个节点。

性质

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

实现

可以 O(n) 实现这个转换。

由于最后一定会留下 n,不妨钦定 n 为根(把 n 拎起来)。

定义:

  • fii 的父节点。
  • dii 的儿子数量。
  • pi 为 prufer 序列的第 i 项。

流程:

定义 i 表示当前编号最小的叶子的指针,cur 为当前序列填到哪一项。

  • pcur=fi
  • dpcur1,表示删去 i 这个节点。
  • 如果 dpcur=0pcur<i,那么 pcur 比当前最小值还要小,直接令 pcur+1=fpcur
void Tree_to_Prufer() {
	for(int i = 1; i < n; ++ i) {
		cin >> f[i];
		++ d[f[i]];
	}
	for(int cur = 0, i = 1; cur < n - 2; ++ i) {
		while(d[i]) ++ i;
		p[++ cur] = f[i];
		while(cur < n - 2 && -- d[p[cur]] == 0 && p[cur] < i) {
			p[cur + 1] = f[p[cur]];
			++ cur;
		}
	}
	for(int i = 1; i <= n - 2; ++ i) cout << p[i] << ' ';
}

从 Prüfer 序列到树#

  1. 找到当前不在 f 中且还未使用的最小元素 x。(等价于是叶子节点)
  2. xf 的第一个元素连边。
  3. 删除 f 的第一个元素,如果 f 非空,重复上述过程。(把叶子删掉,当前节点度数减一)
  4. 最后还剩两个点未使用,将他们连边。

代码实现类似。

void Prufer_to_Tree() {
	for(int i = 1; i <= n - 2; ++ i) {
		cin >> p[i];
		++ d[p[i]];		// 以 $n$ 为根,儿子数量
	}
	p[n - 1] = n;
	for(int cur = 1, i = 1; cur <= n - 1; ++ cur, ++ i) {
		while(d[i]) ++ i;
		f[i] = p[cur];
		while(cur < n - 1 && -- d[p[cur]] == 0 && p[cur] < i) {
			f[p[cur]] = p[cur + 1];
			++ cur;
		}
	} 
	for(int i = 1; i <= n - 1; ++ i) cout << f[i] << ' ';
}

Caylay 定理#

n 个点的有标号无根树有 nn2 种。

例题#

P6086 【模板】Prufer 序列#

submission

P2290 [HNOI2004] 树的计数#

题意:给定每个点的度数 di,求不同的有标号无根树个数。

相当于 di1i 排进长为 n1 的序列的方案数。

(n1)!(d11)!(d21)!(dn1)!

由于保证了答案小于 1017,可以不用高精,全程用一个大于 1017 的质数取模(如 4179340454199820289)。

特判无解,根据 prufer 序列的性质,一定有 di1=n2

submission

CF1267F Foolpruf Security#

题意:二分图 G 左部有 n 个节点,标号 1,2,,n,右部 m 个节点,标号 n+1,n+1,,n+m

满足图 G 是一棵树。

给定其 prufer 序列的两个子序列 a,b,其中 ainbi>n,让你重构一张可能的图,或判无解。

prufer 序列最后剩下的两个点,一定一个在左边,一个在右边。

于是左边删了 m1 个点,则 prufer 序列中不大于 n 的元素被添加 m1 次。

同理,大于 n 的元素右 n1 个。

因此如果 |a|m|b|n,则判无解,否则如果长度不够,随便填满。

代码与模板类似,稍加改动使得左右端点分局两侧。

void Prufer_to_Tree() {
	b[n] = n + m;
	for(int ia = 1, ib = 1, j = 1; ia <= m - 1 || ib <= n; ++ j) {
		while(d[j]) ++ j;
		int cur;
		if(j <= n) {
			cout << j << ' ' << b[ib] << '\n';
			cur = b[ib];
		}
		else {
			cout << j << ' ' << a[ia] << '\n';
			cur = a[ia];
		}
		while(1) {
			if(cur <= n) {
				if(ia <= m - 1 && -- d[a[ia]] == 0 && a[ia] < j) {
					cout << a[ia] << ' ' << b[ib] << '\n';
					++ ia;
					cur = b[ib];
				}
				else break;
				
			}
			else {
				if(ib <= n && -- d[b[ib]] == 0 && b[ib] < j) {
					cout << b[ib] << ' ' << a[ia] << '\n';
					++ ib;
					cur = a[ia];
				}
				else break;
			}
		}
		cur <= n ? ++ ia : ++ ib;
	}
}

submission

BZOJ4766 文艺计算姬#

题意:完全二分图 K(n,m) 的生成树个数。

根据上题结论,将其 prufer 序列分为大于 n 和不大于 n 的两部分,长度分别为 n1m1

所以总方案为 mn1nm1

submission

E-Valuable Forests#

题意:定义树 T 的权值 w(T)=i=1ndeg(i)2,森林 G 的权值为 TGw(T),求所有 n 元有标号森林的权值和。

定义 f(n) 表示不同 n 元森林个数。

枚举元素 n 所在树的大小,在剩下 n1 个元素里选 i1 个与 n 构成一棵树,这棵树有 ii2 种形态。

f(n)=i=1n(n1i1)ii2f(ni)

定义 g(n) 表示不同 n 元树的权值和。

枚举每个点 i 的权值 d,则贡献为满足恰有 d1 个位置是 i 的 prufer 序列个数。

g(n)=i=1nd=1n1d2(n2d1)(n1)n2(d1)

定义 F(n)n 元森林权值和。

枚举 n 所在树的大小 ig(i) 的贡献为 ni 元森林数量,F(ni) 贡献为 ii2

F(n)=i=1n(n1i1)[g(i)f(ni)+F(ni)ii2]

submission

Matrix Tree Theorem#

设无向图 G=(V,E), D=diag(d1,d2,,dn)dii 的度数,G 的拉普拉斯矩阵 L=DE,则 G 的生成树个数为 det(L0),其中 L0L 去掉第 i 行第 i 列(i 任取)。

D=(21243)E=(0001100010000111110110110)

L=(2001101010002111114110113)

删去第 4 行第 4 列:

|L0|=|2001010000211013|=2|100021013|(1)|001100021|=2|2113||0121|=8

posted @   Lu_xZ  阅读(138)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示