Prüfer 序列

Prüfer 序列

Prüfer 序列是将一颗 n 个有标号的点用一个长度为 n2 的序列的表示的方法。

对于一颗有标号的树,会存在唯一一个 Prüfer 序列与之对应。一个 Prüfer 序列也只会对应一颗树。

将一颗树转化为 Prüfer 序列

首先对于所有的叶子节点(此时为 1,2,4,6),选择其编号最小的节点删掉。然后将其父亲节点加入 Prüfer 序列中。

在重复经过 n2 次操作后,剩余了 2 个点,构造结束。

过程

对于一颗有标号的树:

图片取自 oi wiki

显而易见地是,1. 可以用一个小根堆来维护所有的叶子节点。

  1. 每次取出堆中最小的元素删除。

  2. 在每次删除节点后,判断它的父亲节点是否成为叶子节点,如果是,加入堆中。

  3. 重复操作,直至还剩 2 个节点。

for(int i=1;i<n;i++) {
	cin>>u>>v;
    addedge(u,v); addedge(v,u);
	d[u]++; d[v]++;
}
for(int i=1;i<=n;i++) {
    if(d[i]==1) q.push(i);
}
for(int i=1;i<=n-2;i++) {
	int u=q.top(), v; q.pop();
    vis[u]=true;
    for(int k=hd[u];i;i=nxt[i]) {
        if(!vis[to[i]]) v=to[i];
    }
	if(--d[v]==1) q.push(v);
}
for(int i=1;i<=n-2;i++) cout<<p[i]<<' ';

这样的做法的时间复杂度是 O(nlog2n) 的。

线性做法 & 过程

通过维护一个指针 p,初始时 p 指向编号最小的叶子节点,然后

  1. p 指向的节点删掉。判断与这个节点相连的节点是否成为叶子节点。
  2. 如果是,判断该节点的编号 x 是否大于 p 所指向的节点编号。如果大于,不做操作,否则,将节点 x 删掉,并判断与这个节点相连的节点是否成为叶子节点。重复操作 2 直至无法操作。
  3. 将指针 p 自增,直至到一个未被删掉的节点。

性质

可以发现,在 Prüfer 序列中,每个点 u 的出现次数是其点的度数 du1

  1. 对于叶子节点,一定不会存在 Prufer序列中。

  2. 对于非叶子节点 u,在节点 u 被删掉前,会有与之相连的 du1 的节点会被删掉(其原因是在构造 Prufer序列的过程中树是联通的,且树大小始终不小于 2),在这个过程中,节点 u 就会被加入 Prufer序列 du1 次。

由此也可以知道 i=1n(du1)=n2

  1. 剩下的 2 个节点中,其中一个是编号最大的节点,且它绝对不会计入叶子节点中。

Prüfer 序列将转化为一颗树

过程

考虑将上述转化的过程,重复一遍。

  1. 对于 Prüfer 序列的第一个数 u ,和它相连的节点 v 一定是叶子节点中编号最小的。将 uv 连边。

  2. 那么维护一个叶子节点集合 S,将这个编号最小的节点 v 删掉。

  3. 此时将 Prüfer 序列的第一个数也删掉,由于叶子节点在 Prüfer 序列的出现次数为 d1=0,那么如果这个数在 Prüfer 序列中的出现次数变为 0 的话,节点 u 变为叶子节点,加入集合 S 中。

  4. 重复该过程 n2 次。

  5. 最终在集合 S 中会存在 1 个节点,将其和最大节点连接即可。

for(int i=1;i<=n-2;i++) {
    cin>>p[i]; d[p[i]]++;
}
for(int i=1;i<=n;i++) {
    if(!d[i]) q.push(i);
}
for(int i=1;i<=n-2;i++) {
	int u=q.top(); q.pop();
	addedge(p[i],u); addedge(u,p[i]);
	if(--d[p[i]]==0) q.push(p[i]);
}
addedge(n,q.top()); addedge(q.top(),n);

这个过程也可以使用堆维护,当然也存在线性做法。

线性做法 & 过程

同样是维护一个指针指向叶子节点中的编号最小的节点。

性质

  1. 无向完全图的不同生成树数(Cayley公式)

对于一个 n 个点的无向完全图,任意一个长度为 n2 的 Prüfer 序列都可以构造出一个不同的具有 n 个点的树。

所以讲,会有 nn2 个不同的 Prüfer 序列构成 n 个点的树,那么无向完全图的不同生成树数也就是 nn2 个。

  1. n 个点无根树计数,且已知每个点的点度。

由于每个点在 Prüfer 序列中的出现次数为 di1,所以可以利用组合知识求出。

(n2b11)((n2)(b11)b21)(bn1bn1)

展开可以得到

(n2)!i=1n(bi1)

posted @   Ciaxin  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示