preparing

「数树题」- 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\) 为:

\[\begin{aligned} Ans &= \sum\limits_{\sum_{i=1}^{k}\,d_i = 2k-2}\dfrac{(k-2)!}{\prod_{i=1}^k (d_i-1)!}\times\prod\limits_{i-1}^k s_i^{d_i} \\ &= \sum\limits_{\sum_{i=1}^{k}\,f_i = k-2}\dfrac{(k-2)!}{\prod_{i=1}^k f_i!}\times\prod\limits_{i-1}^k s_i^{f_i+1}\qquad(令 f_i = d_i+1) \\ &= [\sum\limits_{\sum_{i=1}^{k}\,f_i = k-2}\dfrac{(k-2)!}{\prod_{i=1}^k f_i!}\times\prod\limits_{i-1}^k s_i^{f_i}]\times \prod_{i=1}^k s_i \\ &= (\sum\limits_{i=1}^k s_i)^{k-2}\times \prod_{i=1}^k s_i \qquad(根据\ (\sum\limits_{i=1}^m x_i)^p = \sum\limits_{\sum_{i=1}^m \,c_i = p}\dfrac{p!}{\prod_{i=1}^m c_i!}\times\prod\limits_{i=1}^m x_i^{c_i}\ ) \end{aligned} \]

#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;
}
posted @ 2023-07-04 19:01  qzhwlzy  阅读(35)  评论(0编辑  收藏  举报