所谓 Prufer 序列,就是 Prufer 发明的序列。
(逃)
前言
优雅的神奇魔术。
看名字很高大难,但实际上是高大清(小清新)。
很简单的建立起树与序列之间的双射,且这个序列的性质非常良好,且这个序列的性质与度数密切相关。
能优雅简洁的证明一些恶心的结论。
注意! Prufer序列不考虑只有一个结点的树。
解析
定义
把一棵树转化为 Prufer 序列的流程如下:
- 找到当前度数为1且编号最小的点 xxx。
- 将 xxx 点删去,将唯一与 xxx 相连的点 fff (可以理解为以n作根情况下的“父亲”)加入 Prufer 序列。
- 将 fff 的度数减一。
- 不断执行 1-3,直到只剩下两个点。
最终我们得到的长度为 n−2n-2n−2 的序列即最终的 Prufer 序列。
性质
其有如下性质:
- 序列中的每个数都是 [1,n][1,n][1,n] 之间。
- 一个度数为 dxd_xdx 的点在序列中出现 dx−1d_x-1dx−1 次。
- 最终剩下的两个点中必然有一个点为 nnn。
都较为显然。
把树转化为 Prufer 序列
利用 Prufer 序列的定义,开一个堆存当前的度数为1的点,即可 O(nlogn)O(n\log n)O(nlogn) 的构造。
但可以做到线性。
维护一个指针 ppp,从1扫到n,表示当前编号最小的一度点。每次后移指针知道找到一个一度点,将其加入 Prufer 序列,如果父亲减完度数变成了一度点且编号小于 ppp 则将父亲加入序列,并递归的考虑祖父,否则直接忽略。
注意这么做最后会得到一个 n−1n-1n−1 的序列,因为最后会把和 nnn 相连的点也删去。把序列尾抹掉即可。
for(int i=1;i<n;i++){fa[i]=read();du[i]++;du[fa[i]]++;}for(int p=1;p<n;p++){if(du[p]>1) continue;q[++tot]=fa[p];int f=fa[p];--du[f];while(du[f]==1&&f<p){q[++tot]=fa[f];f=fa[f];--du[f];}}--tot;
把 Prufer 序列转化为树
利用 Prufer 序列的性质2,我们可以得到每个点的度数。
每次找到最小的一度点,它的父亲就是当前序列的队首元素,将其与队首连边,队首的度数减一,并后移队首指针。
和树转化为 Prufer 序列类似的,我们也可以维护一个指针 ppp 做到线性。
注意,由于 Prufer 序列的长度只有 n-2,我们必然只为 n-2 个结点分配了“父亲”(即连了n-2条边),最后我们最后找到那个没有被分配父亲的非 n 的点,将其与 n 相连即可。
for(int i=1;i<=n;i++) du[i]=1;for(int i=1;i<=n-2;i++){q[i]=read();++du[q[i]];}int pl=1;for(int p=1;p<n&&pl<=n-2;p++){if(du[p]>1) continue;int x=p;while(x<=p&&du[x]==1&&pl<=n-2){fa[x]=q[pl];du[q[pl]]--;x=q[pl];++pl;}}for(int i=1;i<n;i++) if(!fa[i]) fa[i]=n;
凯莱定理
通过上面的构造我们可以知道,有标号无根树和Prufer序列是双射关系。
Prufer 序列的个数显然为 nn−2n^{n-2}nn−2 个,那么我们也就自然得出了凯莱定理:
nnn 个结点的有标号无根树有 nn−2n^{n-2}nn−2 个。