(prufer 序列仅对 \(n>1\) 有效,\(n=1\) 一般要特判)
prufer 序列是 \(n\) 个点的有标号无根树集合与 \(([1,n]\cap\Z)^{n-2}\) 的一种双射方式,可以将不会处理的树形结构转化为数组,在很多计数题里很有用。
下面先给出 prufer 序列的构造方式(即定义),然后给出通过任意长度为 \(n-2\) 每个元素 \(\in[1,n]\) 的序列作为 prufer 序列还原出存在唯一的树的方式(也就自然证明了双射性)。
从树构造 prufer 序列
一次操作指找到当前编号最小的叶子,将它唯一连向的点的编号 append 进 prufer 序列,并且删除该叶子。进行 \(n-2\) 次操作,还剩下两个由一条边相连的点,此时得到的长度为 \(n-2\) 的序列则为 prufer 序列。
注意到 \(n\) 永远不会成为被找到的叶子,所以可以钦定 \(n\) 为根,那么每次叶子唯一连向的点就是父亲,这样使实现更方便。考虑实时维护各节点的儿子个数,等于 \(0\) 则及时加入叶子集合,每次从叶子集合里找最小的,可以用堆简单地线对维护。
事实上有线性做法。注意到每次插入后必找最小值,那就可以看刚刚插入的是否最小值,如果是的话就直接用掉,否则才加入叶子集合。这样一来叶子集合的最小值是有单调性的,可以直接用桶存,然后对最小值「one-pointer」即可。
code:
void prf_init(){
for(int i=1;i<n;i++)deg[fa[i]]++;
for(int i=1;i<=n;i++)if(!deg[i])hav[i]++;
int now=1,least=0,d;
for(int i=1;i<=n-2;i++){
if(least)d=least,least=0;
else{while(!hav[now])now++;d=now;hav[now]--;}
prf[i]=fa[d];
if(!--deg[fa[d]])if(fa[d]<now)least=fa[d];else hav[fa[d]]++;
}
}
从 prufer 序列还原树
通过 prufer 序列的定义易知 \(i\) 在 prufer 序列中出现的次数就等于 \(\deg_i-1\)(对 \(x\neq n\),\(x\) 的儿子全被删除了;对 \(x=n\),\(x\) 恰有一个儿子没被删除,但相比非根节点没有父亲对度数的贡献)。那么通过 prufer 序列就可以知道每个点的儿子个数,一开始也就能知道叶子集合。
考虑从左往右通过已知的叶子集合模拟 prufer 序列的构造过程。每次找到最小叶子、删除,可知当前最小叶子跟当前 prufer 值有一条边。如此 \(n-2\) 次之后连了 \(n-2\) 条边,还剩一条边是剩下两个点的边,其中一个是 \(n\),另一个就在 \([1,n)\) 里找一下谁还没爸爸。
通过叶子集合模拟的过程就跟上面构造 prufer 序列的线性做法一模一样地做即可。所以说构造和还原的代码其实基本一样。
code:
void fa_init(){
for(int i=1;i<=n-2;i++)deg[prf[i]]++;
for(int i=1;i<=n;i++)if(!deg[i])hav[i]++;
int now=1,least=0,d;
for(int i=1;i<=n-2;i++){
if(least)d=least,least=0;
else{while(!hav[now])now++;d=now;hav[now]--;}
fa[d]=prf[i];
if(!--deg[fa[d]])if(fa[d]<now)least=fa[d];else hav[fa[d]]++;
}
for(int i=1;i<n;i++)if(!fa[i])fa[i]=n;
}
由此可得一个小结论(当然 prufer 序列的应用远不止于此):\(n>1\) 个点的有标号无根树数量为 \(n^{n-2}\)。其实用矩阵树定理也能证(只不过稍烦),就胡个证明吧(就当练习一下线代吧)。
其实就是无向图生成树个数,造出基尔霍夫矩阵,只要证 \(n-1\) 阶行列式
\[\begin{vmatrix}n-1&-1&-1&\cdots&-1\\-1&n-1&-1&\cdots&-1\\-1&-1&n-1&\cdots&-1\\\vdots&\vdots&\vdots&\ddots&-1\\-1&-1&-1&-1&n-1\end{vmatrix}=n^{n-2} \]将第一行作为减数减到其他所有行上,设 \(x\) 阶方阵 \(K_x\) 为
\[\begin{bmatrix}n-1&-1&-1&\cdots&-1\\-n&n\\-n&&n\\\vdots&&&\ddots\\-n&&&&n\end{bmatrix} \]只要证 \(|K_{n-1}|=n^{n-2}\)。事实上我们有 \(|K_x|=(n-x)n^{x-1}\)(这是证明不是推导/xyx),下面按 \(x\) 归纳证明之。
\(|K_1|=n-1\) 显然满足。当 \(x>1\) 时,将 \(K_x\) 按第二行展开:\((2,1)\) 余子式是个上三角矩阵,显然等于 \(-n^{x-2}\);\((2,2)\) 余子式显然是 \(|K_{x-1}|\);其他位置都是 \(0\),无贡献。假设 \(K_{x-1}\) 满足,有
\[\begin{aligned}|K_x|&=(-1)^{2+1}(-n)\!\left(-n^{x-2}\right)+(-1)^{2+2}n(n-x+1)n^{x-2}\\&=-n^{x-1}+(n-x+1)n^{x-1}\\&=(n-x)n^{x-1}\end{aligned} \]证毕!