「数树题」- Prüfer 序列
简介
Prüfer 序列(以下为方便写作 “prufer 序列”)可以将一个带标号的 \(n\) 个结点的树用 \([1,n]\) 中的 \(n-2\) 个整数表示,也可以理解为完全图的生成树与数列之间的双射。
构造
树到序列
prufer 序列的简历过程为:选取树中所有叶子节点中编号最小的,将其的父节点加入序列末并删除该叶子节点。重复直至只剩最后两个节点。如下图例子所示。
实现显然可以使用堆模拟上述过程,时间复杂度显然为 \(\mathcal{O}(n\log n)\)。下面将其优化至线性。
注意到,在删除一个叶子节点时,最多只会将一个节点(其父节点)加入堆,即堆中元素个数单调不增。那么,我们不妨不使用堆,从小到大枚举所有点 \(p\),如果 \(p\) 是叶子节点则直接操作,若发现其父节点变为叶子节点且编号小于 \(p\),则直接操作。如此下去,复杂度为 \(\mathcal{O}(n)\)。
序列到树
显然,prufer 序列中一个数出现的次数就是其子节点数量,那么我们可以用同样的思想完成树的构造。
例 1:P6086 【模板】Prüfer 序列
#include<iostream>
#include<cstdio>
#define maxn 5000005
#define ll long long
using namespace std;
int n,m,a[maxn],son[maxn],pos,now,count=0; ll ans=0; int fa[maxn];
int main(){
scanf("%d%d",&n,&m); for(int i=1;i<n-(m==2);i++){scanf("%d",&a[i]); son[a[i]]++;}
for(int i=1;i<=n;i++) if(!son[i]){pos=now=i; break;}
if(m==1){
while(count<n-2){
ans^=1LL*(++count)*a[now]; son[a[now]]--;
if(!son[a[now]]&&a[now]<pos) now=a[now]; else{do pos++; while(son[pos]); now=pos;}
} printf("%lld",ans);
}else{
while(count<n-2){
count++; ans^=1LL*now*a[count]; son[a[count]]--;
if(!son[a[count]]&&a[count]<pos) now=a[count]; else{do pos++; while(son[pos]); now=pos;}
} ans^=1LL*now*n; printf("%lld",ans);
}
return 0;
}
性质
无根树/有根数计数
若有 \(n\) 个节点的无根树,其任意 prufer 序列都能构造唯一的树,故一共有 \(n^{n-2}\) 种。此即 Cayley 公式。
有根树则 \(n^{n-2}\times n = n^{n-1}\) 种。
若限定点 \(i\) 的度数为 \(d_i\),则无根树个数有 \(\dfrac{(n-2)!}{\prod\limits_{i=1}^n (d_i-1)!}\) 种。
例 2:CF156D Clues
题目大意:给定一个 \(n\) 个点 \(m\) 条边的带标号无向图,包含 \(k\) 个连通块,求添加 \(k−1\) 条边使得整个图连通的方案数,答案对 \(p\) 取模。
设第 \(i\) 个联通块的大小为 \(s_i\),度数(不算内部的)为 \(d_i\),则答案 \(Ans\) 为:
#include<iostream>
#include<cstdio>
#define maxn 100005
#define ll long long
using namespace std;
int n,m,u,v; int fa[maxn],siz[maxn]; bool vis[maxn]; ll mod,k,ans=1;
int getfa(int p){if(fa[p]==p) return p; return fa[p]=getfa(fa[p]);}
ll qp(ll di,ll mi){ll res=1LL; while(mi){if(mi&1) res=res*di%mod; di=di*di%mod; mi/=2;} return res;}
int main(){
scanf("%d%d%lld",&n,&m,&mod); for(int i=1;i<=n;i++){fa[i]=i; siz[i]=1;} for(int i=1;i<=m;i++)
{scanf("%d%d",&u,&v); int r1=getfa(u),r2=getfa(v); if(r1!=r2){fa[r2]=r1; siz[r1]+=siz[r2];}}
for(int i=1;i<=n;i++){int rt=getfa(i); if(!vis[rt]){vis[rt]=1; ans*=siz[rt]; k++;}}
printf("%lld",ans*qp(n,k-2)); return 0;
}