定义

purfer 序列构造了一组树和序列之间的双射,及确定 prufer 序列可以确定树的形态,知道树的形态可以找到单一的 prufer 序列。

构造方法

  1. 每次找到编号最小的叶节点,删掉它并在序列末尾增加它连接到的节点。
  2. 重复 n−2n-2 次之后,只剩下两个结点,算法结束。
    注意:这里的树是一棵无根树所以根节点的意义是度为 11 的节点。

树转化为 prufer

首先需要知道性质:nn 号节点在构造 prufer 序列的时候永远都不会被删除。因为这是一棵无根树,那么叶子节点即使在是一条链的情况下也至少有 22 个。无论 nn 号节点是否是叶子节点,它都因为编号不是最小的而不会被删除。为了便于实现可以将 nn 看为根节点建树,但是注意这是种是以可无根树,这样只是便于实现而已。

对于平凡的做法,使用堆在 O(nlog⁡n)O(n\log n) 的时间内求解。

  1. 将所有叶子节点放入一个小根堆中。
  2. 将堆顶出队,访问其在树上联通的点并更新新节点的度,同时将这个点记录在 purfer 序列中。
  3. 如果度为 11 那么将这个节点入队,接着执行操作 22 直到树上的只有 22 个节点没有入队。
  4. 执行上述操作知道树内存在的节点不多于 22 个。

对于优雅的方式,其实还有一种线性的算法可以在 O(n)O(n) 内求解。因为所有删除的点都是叶子节点,所以每删除一个,新增加的叶子节点定然只有一个。所以我们可以维护一个指针 pp 指向最小的叶子节点,进行高效的操作。

  1. 删除 pp 节点,接着检查新增的叶子节点 xx。
  2. 如果 x>px>p,那么暴力的将 pp 向后移动找到新的叶子节点,接着执行操作 11。
  3. 如果 x<px<p,那么直接删除 xx 节点然后反复执行操作 33,在不满足条件后进行操作 22。
  4. 执行上述操作知道树内存在的节点不多于 22 个。

在操作后可以发现 prufer 序列还有一个性质:每一个在节点为序列中出现的次数为它的度 −1-1。首先考虑叶子节点,因为他们的父亲一定不会被删除所以它永远不会出现在 prufer 序列中,满足性质。假设一个点的度不为 11,那么在一 nn 为根的情况下,它的父亲也永远不会被删去。它的所有的孩子节点都会有这个节点的后代被删除,所以它的所有的儿子节点都会成为叶子节点。而在它的孩子节点被删除的它的度 −1-1 次后,它也就在 prufer 序列中出现度 −1-1 次了。

prufer 转化为树

因为 prufer 序列的性质,通过 prufer 序列我们可以得到每一个节点的度。

  1. 维护一个指针 𝑝𝑝,指向当前编号最小的度数等于 𝟏𝟏 的结点。
  2. 容易得到 pp 点一定与当前 prufer 序列的第一个节点连接,那么直接连边即可。
  3. 修改连边的两个节点的度,接下来的操作就与树转化为 prufer 一样了。
  4. 在 purfer 序列操作结束后将序列中的最后一个数与 nn 好节点连边即可。

例题:【模板】Prufer 序列

Link

#include<iostream>
using namespace std;
const int N=5e6+5;
int n,m,fa[N],prufer[N],du[N];
long long ans;
void tree_to_prufer(){
	for(int i=1;i<n;i++){
		cin>>fa[i];
		du[fa[i]]++;
	}
	for(int i=1,p=1;i<=n-2;i++,p++){
		while(du[p]){
			p++;
		}
		prufer[i]=fa[p];
		while(i<=n-2&&!--du[prufer[i]]&&prufer[i]<p){
			prufer[i+1]=fa[prufer[i]];
			i++;
		}
	}
	for(int i=1;i<=n-2;i++){
		ans^=1ll*i*prufer[i];
	}
}
void purfer_to_tree(){
	for(int i=1;i<=n-2;i++){
		cin>>prufer[i];
		du[prufer[i]]++;
	}
	prufer[n-1]=n;
	for(int i=1,p=1;i<n;i++,p++){
		while(du[p]){
			p++;
		}
		fa[p]=prufer[i];
		while(i<n&&!--du[prufer[i]]&&prufer[i]<p){
			fa[prufer[i]]=prufer[i+1];
			i++;
		}
	}
	for(int i=1;i<n;i++){
		ans^=1ll*i*fa[i];
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin>>n>>m;
	if(m==1){
		tree_to_prufer();
	}
	else{
		purfer_to_tree();
	}
	cout<<ans<<'\n';
	return 0;
}