Prufer 序列

Prufer 序列

Prufer 序列可以将一个带标号 \(n\) 个结点的树用 \([1,n]\) 中的 \(n-2\) 个整数表示。你也可以把它理解为完全图的生成树与数列之间的双射,常用于组合计数问题上。

对树建立 Prufer 序列

Prufer 是这样建立的:每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点。重复 \(n-2\) 次后就只剩下两个结点,算法结束。

线性构造

线性构造的本质就是维护一个指针指向我们将要删除的结点。首先发现,叶结点数是非严格单调递减的。要么删一个,要么删一个得一个。

过程

于是我们考虑这样一个过程:维护一个指针 \(p\)。初始时 \(p\) 指向编号最小的叶结点。同时我们维护每个结点的度数,方便我们知道在删除结点的时侯是否产生新的叶结点。操作如下:

  1. 删除 \(p\) 指向的结点,并检查是否产生新的叶结点。
  2. 如果产生新的叶结点,假设编号为 \(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 序列的性质

  1. 在构造完 Prufer 序列后原树中会剩下两个结点,其中一个一定是编号最大的点 \(n\)
  2. 每个结点在序列中出现的次数是其度数减 \(1\)。(没有出现的就是叶结点)

用 Prufer 序列重建树

重建树的方法是类似的。根据 Prufer 序列的性质,我们可以得到原树上每个点的度数。然后你也可以得到编号最小的叶结点,而这个结点一定与 Prufer 序列的第一个数连接。然后我们同时删掉这两个结点的度数。
每次我们选择一个度数为 \(1\) 的最小的结点编号,与当前枚举到的 Prufer 序列的点连接,然后同时减掉两个点的度。到最后我们剩下两个度数为 \(1\) 的点,其中一个是结点 \(n\)。就把它们建立连接。使用堆维护这个过程,在减度数的过程中如果发现度数减到 \(1\) 就把这个结点添加到堆中,这样做的复杂度是 \(O(n\log n)\) 的。

线性时间重建树

同线性构造 Prufer 序列的方法。在删度数的时侯会产生新的叶结点,于是判断这个叶结点与指针 \(p\) 的大小关系,如果更小就优先考虑它

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;
}

edit

posted @ 2023-02-19 14:56  雨夜风月  阅读(38)  评论(0编辑  收藏  举报