Prufer 序列
Prufer 序列可以将一个带标号 \(n\) 个结点的树用 \([1,n]\) 中的 \(n-2\) 个整数表示。你也可以把它理解为完全图的生成树与数列之间的双射,常用于组合计数问题上。
对树建立 Prufer 序列
Prufer 是这样建立的:每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点。重复 \(n-2\) 次后就只剩下两个结点,算法结束。
线性构造
线性构造的本质就是维护一个指针指向我们将要删除的结点。首先发现,叶结点数是非严格单调递减的。要么删一个,要么删一个得一个。
过程
于是我们考虑这样一个过程:维护一个指针 \(p\)。初始时 \(p\) 指向编号最小的叶结点。同时我们维护每个结点的度数,方便我们知道在删除结点的时侯是否产生新的叶结点。操作如下:
- 删除 \(p\) 指向的结点,并检查是否产生新的叶结点。
- 如果产生新的叶结点,假设编号为 \(x\),我们比较 \(p,x\) 的大小关系。如果 \(x>p\),那么不做其他操作;否则就立刻删除 \(x\),然后检查3. 删除 \(x\) 后是否产生新的叶结点,重复 \(2\) 步骤,直到未产生新节点或者新节点的编号 \(>p\)。
让指针 \(p\) 自增直到遇到一个未被删除叶结点为止;
性质
循环上述操作 \(n-2\) 次,就完成了序列的构造。接下来考虑算法的正确性。
\(p\) 是当前编号最小的叶结点,若删除 \(p\) 后未产生叶结点,我们就只能去寻找下一个叶结点;若产生了叶结点 \(x\):
- 如果 \(x>p\),则反正 \(p\) 往后扫描都会扫到它,于是不做操作;
- 如果 \(x<p\),因为 \(p\) 原本就是编号最小的,而 \(x\) 比 \(p\) 还小,所以 \(x\) 就是当前编号最小的叶结点,优先删除。删除 \(x\) 继续这样的考虑直到没有更小的叶结点。
算法复杂度分析,发现每条边最多被访问一次(在删度数的时侯),而指针最多遍历每个结点一次,因此复杂度是 \(O(n)\) 的。
Prufer 序列的性质
- 在构造完 Prufer 序列后原树中会剩下两个结点,其中一个一定是编号最大的点 \(n\)。
- 每个结点在序列中出现的次数是其度数减 \(1\)。(没有出现的就是叶结点)
用 Prufer 序列重建树
重建树的方法是类似的。根据 Prufer 序列的性质,我们可以得到原树上每个点的度数。然后你也可以得到编号最小的叶结点,而这个结点一定与 Prufer 序列的第一个数连接。然后我们同时删掉这两个结点的度数。
每次我们选择一个度数为 \(1\) 的最小的结点编号,与当前枚举到的 Prufer 序列的点连接,然后同时减掉两个点的度。到最后我们剩下两个度数为 \(1\) 的点,其中一个是结点 \(n\)。就把它们建立连接。使用堆维护这个过程,在减度数的过程中如果发现度数减到 \(1\) 就把这个结点添加到堆中,这样做的复杂度是 \(O(n\log n)\) 的。
线性时间重建树
同线性构造 Prufer 序列的方法。在删度数的时侯会产生新的叶结点,于是判断这个叶结点与指针 \(p\) 的大小关系,如果更小就优先考虑它
AC·code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
namespace Q{
il int rd(){
ri int x=0;ri bool f=0;ri char c=getchar();
while(!isdigit(c)) f|=(c==45),c=getchar();
while(isdigit(c)) x=x*10+(c^48),c=getchar();
return f?-x:x;
}
il void wt(long long x){
if(x<0) x=-x,putchar(45);
if(x>=10) wt(x/10);
return putchar(x%10|48),void();
}
} using namespace Q;
cs int N=5e6+1;
namespace ope{
int p[N],f[N],deg[N];
il long long getp(int n){
ri long long as=0;
for(ri int i=1;i<n;++i){
f[i]=rd(),deg[f[i]]++;
}
for(ri int i=1,j=1;i<n-1;++i,++j){
while(deg[j]) ++j; p[i]=f[j];
while(i<n-1&&(!--deg[p[i]])&&p[i]<j){
p[i+1]=f[p[i]],++i;
}
}
for(ri int i=1;i<n-1;++i) as^=1ll*i*p[i];
return as;
}
il long long getf(int n){
ri long long as=0;
for(ri int i=1;i<n-1;++i){
p[i]=rd(),deg[p[i]]++;
} p[n-1]=n;
for(ri int i=1,j=1;i<n;++i,++j){
while(deg[j]) ++j; f[j]=p[i];
while(i<n&&(!--deg[p[i]])&&p[i]<j){
f[p[i]]=p[i+1],++i;
}
}
for(ri int i=1;i<n;++i) as^=1ll*i*f[i];
return as;
}
} using namespace ope;
signed main(){
ri int n=rd(),m=rd();
m==1?wt(getp(n)):wt(getf(n));
return 0;
}