Loading

prufer 序列浅谈

prufer 序列完成了从一棵大小为 \(n\) 的无根树到长度为 \(n-2\) 的序列的双射,下面简述其构造过程:

从一棵无根树到 prufer 序列:

我们找到其编号最小的叶子,然后删掉叶子,把其父亲加入队列。重复操作,直到整棵树剩下两个节点。

\(O(n\log n)\) 是显然可以做的,我们考虑如何 \(O(n)\)

用一个指针,指向当前编号最小的叶子节点,然后删掉他,如果其父亲变成了叶子,并且编号比它小,我们就删掉其父亲并重复操作,否则我们往后推指针,找到下一个叶子。

从 prufer 序列到一棵无根树

容易发现,每个节点的度数是在 prufer 序列中的出现次数加 \(1\),考虑我们现在知道每个点的度数,然后我们找到编号最小的叶子,然后删掉它,如果其父亲也变成了叶子,并且编号较小,我们重复这个操作,否则的话,我们考虑往后推指针,找到下一个叶子。最后会剩下两个点,其中一个节点是 \(n\),我们只需要在 prufer 序列最后加入 \(n\) 就可以避免特判。

代码:


#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define int long long
#define ull unsigned long long
#define mset(a,b) memset(a,b,sizeof(a))
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define N 5000100
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int n,m,d[N];

namespace Subtask1{

    int k=INF,fa[N],p[N];

    inline void Solve(){
        for(int i=1;i<=n-1;i++){
            int x;read(x);
            d[x]++;d[i]++;fa[i]=x;
        }
        for(int i=1,j=1;i<=n-2;i++,j++){
            while(d[j]!=1) j++;p[i]=fa[j];d[j]--;d[fa[j]]--;
            while(i<=n-2&&d[p[i]]==1&&p[i]<j){p[i+1]=fa[p[i]];d[p[i]]--;d[fa[p[i]]]--;i++;}
        }
        int ans=0;
        for(int i=1;i<=n-2;i++) ans=(ans^(i*p[i]));
        printf("%lld\n",ans);
    }
}

namespace Subtask2{

    int fa[N],p[N],d[N];

    inline void Solve(){
        for(int i=1;i<=n-2;i++) read(p[i]),d[p[i]]++;
        p[n-1]=n;
        for(int i=1;i<=n;i++) d[i]++;
        for(int i=1,j=1;i<=n-1;i++,j++){
            while(d[j]!=1) j++;fa[j]=p[i];d[j]--;d[p[i]]--;
            while(i<=n-1&&d[p[i]]==1&&p[i]<j){fa[p[i]]=p[i+1];d[p[i]]--;d[fa[p[i]]]--;i++;}
        }
        int ans=0;
        for(int i=1;i<=n-1;i++) ans=(ans^(i*fa[i]));
        printf("%lld\n",ans);
    }
}

signed main(){
    read(n);read(m);
    if(m==1) Subtask1::Solve();else Subtask2::Solve();
    return 0;
} 

不过更需要注意的是,上面我们定义的叶子是不严谨的。

注意,prufer 序列是有标号无根树与长度为 \(n-1\) 序列的一种双射,由此可以知道有标号无根树的个数为 \(n^{n-2}\) 个。而我们所谓的叶子,实际上就是度数为 \(1\) 的点。而洛谷上之所以定义了父亲等,是因为我们以 \(n\) 为根,而其一定是不会被删掉的。

经过一系列的化简,我么你可以得到比 Cayley 定理(上面那个有标号无根树个数的定理)更强的定理,如果给定了 \(k\) 个连通块,总共连了 \(n\) 个点,把这些连通块练成树的方案数应该为:

\[ n^{k-2}\prod s_i \]

其中 \(s_i\) 表示第 \(i\) 个连通块的大小。

我们考虑如何证明,首先不关注这些连通块内部的点,设完边后第 \(i\) 个连通块的度数为 \(d_i\),那么我们有:

\[ \sum\limits_{d_i\ge 1,\sum d_i=2k-2}\dbinom{k-2}{d_1-1,d_2-1,...d_k-1} \]

上面这个式子是因为我们度数为 \(x\) 的点在 prufer 序列中的出现次数为 \(x-1\)

然后关注到上面这个式子每一个方案都对应着一棵树,而每个连通块都需要选出一个点来进行链接,由此我们可以得到总的方案数应该为:

\[ \sum\limits_{d_i\ge 1,\sum d_i=2k-2}\dbinom{k-2}{d_1-1,d_2-1,...d_k-1}\prod s_i^{d_i} \]

根据多元二项式定理进行化简,我们就可以得到上面给定的那个式子。

posted @ 2022-02-28 17:29  hyl天梦  阅读(52)  评论(0编辑  收藏  举报