Prüfer 序列学习笔记

Prüfer 序列学习笔记

知识点#

前言#

Prüfer 序列是为了证明 Cayley 公式而被发明出来的,即一个 n 个点的完全图共有 nn2 个不同的树。

Prüfer 序列可以将一个 n 个点的树唯一映射到一个长度为 n2 的序列上,即两棵树不同当且仅当它们的 Prüfer 序列不同。

Prüfer 序列的构造#

每次找到编号最小的一个叶子节点,将与其相连的节点加入序列,并将其在树上删除,直到树上只有两个节点。

Prüfer 序列具有以下两个显然的性质:

  • 树中最后剩下的两个点中,一定存在一个节点是编号最大的点 n
  • 每个节点在序列中出现的次数为该节点的度数 1

Prüfer 序列的重构树#

下面证明 Prüfer 序列可以唯一表示一棵树。

依次考虑 Prüfer 序列 p 中的每一个元素,假设当前考虑到第 i 个元素 pi,不难发现未被标记的编号最小的叶子节点是好确定的。在未被标记的节点中,编号最小的不在 p[i:n] 中出现的节点 x 就是当前未被标记的编号最小的叶子节点,将 x 标记为叶子节点。以此类推,可以确定 n2 条边。而最后一条边的一个端点一定是 n,另一个端点可以通过上面的方法确定。证毕。

存在一个更为优秀的线性做法。

令指针 x 指向编号最小的叶子节点,将其删除,删除该叶子节点后一定会使得一个节点的度数 1,若出现一个新的叶子节点 y,且 y<x,就继续删除,重复上述操作直至新出现的叶子节点比 x 大或没有新的叶子节点。

多次进行上面的操作就可以得到重构树。

例题#

CF156D Clues#

小 D 最近在幼儿园学习了图论知识。
他知道对于一个 n 个点 m 条边的带标号无向图 G,若形成了 k 个连通块,最少增加 k1 条边就可以使得图 G 连通。
但是小 D 突发奇想,他想知道增加 k1 条边使得整个图连通的方案数对他喜欢的模数 p 取模的结果。
对于全部数据,1n,m1051p109

设增加 k1 条边之后,第 i 个连通块的度数为 di

对于给定序列 {di},Prüfer 序列的数量为

(k2d11,d21,,dk1)=(k2)!(d11)!(d21)!(dk1)!

其中,si 表示第 i 个连通块中的节点数量。

对于给定序列 {di},使图连通的方案数为

(k2d11,d21,,dk1)i=1ksidi

枚举序列 {di},使图连通的方案数为

di1,i=1kdi=2k2(k2d11,d21,,dk1)i=1ksidi

ei=di1ei0,i=1kei=k2(k2e1,e2,,ek)i=1ksiei+1

考虑多元二项式定理:

(x1++xm)p=ci0,i=1mci=p(pc1,c2,,cm)i=1mxici

可以将原式变为

(i=1ksk)k2i=1ksi=nk2i=1ksi

点击查看代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 1000010
int n,m,mod;
int fa[N],siz[N];

int read ()
{
	int k=1,s=0;char ch=getchar ();
	while (!isdigit (ch)) {if (ch=='-') k=-1;ch=getchar ();}
	while (isdigit (ch)) {s=s*10+ch-'0';ch=getchar ();}
	return k*s;
}

int Qpow (int x,int y)
{
	int res=1;
	while (y>0)
	{
		if (y&1) res=1ll*res*x%mod;
		x=1ll*x*x%mod,y>>=1;
	}
	return res;
}

int Find (int x)
{
	return x==fa[x]?x:fa[x]=Find (fa[x]);
}

void Merge (int x,int y)
{
	int rx=Find (x),ry=Find (y);
	if (rx!=ry)
	{
		fa[ry]=rx;
		siz[rx]+=siz[ry];
	}
}

void Init ()
{
	n=read (),m=read (),mod=read ();
	for (int i=1;i<=n;i++)
		fa[i]=i,siz[i]=1;
	for (int i=1;i<=m;i++)
	{
		int x=read (),y=read ();
		Merge (x,y);
	}
}

void Work ()
{
	int cnt=0,ans=1;
	for (int i=1;i<=n;i++)
		if (i==Find (i))
		{
			cnt++;
			ans=1ll*ans*siz[i]%mod;
		}
	if (cnt==1) ans=1%mod;
	else ans=1ll*ans*Qpow (n,cnt-2)%mod;
	printf ("%d\n",ans);
}

signed main ()
{
	Init ();
	Work ();
	return 0;
}

AT_arc162_d [ARC162D] Smallest Vertices#

给定一个使得其总和为 N1 的非负整数序列 d=(d1,d2,,dN)
对于带编号从 1N 的顶点,假设 1 是根,我们将其点度数定义为 di
我们称满足以下条件的 根向树好树
i 的出度是 di
此外,对于好树的顶点 v,定义 f(v) 为以 v 为根的子树中(包括 v)顶点编号的最小值。我们将满足 f(v)=v 的顶点称为好顶点
求好树中所有好顶点的总数,将其对 998244353 取模后的余数。
对于全部数据,2N5000diN1d11i=1Ndi=N1

点击查看代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 505
const int mod=998244353;
int n,deg[N];
int fac[N],ifac[N],F[N][N][N];

int read ()
{
	int k=1,s=0;char ch=getchar ();
	while (!isdigit (ch)) {if (ch=='-') k=-1;ch=getchar ();}
	while (isdigit (ch)) {s=s*10+ch-'0';ch=getchar ();}
	return k*s;
}

int Qpow (int x,int y)
{
	int res=1;
	while (y>0)
	{
		if (y&1) res=1ll*res*x%mod;
		x=1ll*x*x%mod,y>>=1;
	}
	return res;
}

void Init ()
{
	n=read ();
	fac[0]=1;
	for (int i=1;i<=n;i++)
		fac[i]=1ll*fac[i-1]*i%mod;
	ifac[n]=Qpow (fac[n],mod-2);
	for (int i=n-1;i>=0;i--)
		ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
	for (int i=1;i<=n;i++)
		deg[i]=read ();
}

void Work ()
{
	F[n+1][0][0]=1;
	for (int i=n;i>=1;i--)
		for (int j=0;j<=n-i;j++)
			for (int k=0;k<n;k++)
			{
				F[i][j][k]=(F[i][j][k]+F[i+1][j][k])%mod;
				F[i][j+1][k+deg[i]]=(F[i][j+1][k+deg[i]]+F[i+1][j][k])%mod;
			}
	int res=fac[n-2],ans=0;
	for (int i=1;i<=n;i++)
		if (i==1) res=1ll*res*ifac[deg[i]-1]%mod;
		else res=1ll*res*ifac[deg[i]]%mod;
	for (int i=1;i<=n;i++)
	{
		if (i==1 || deg[i]==0)
		{
			ans=(ans+res)%mod;
			continue;
		}
		for (int j=deg[i]+1;j<=n-i+1;j++)
			ans=(ans+1ll*F[i+1][j-1][j-1-deg[i]]*deg[i]%mod*fac[j-2]%mod*fac[n-j-1]%mod*ifac[n-2]%mod*res%mod)%mod;
	}
	printf ("%d\n",ans);
}

signed main ()
{
	Init ();
	Work ();
	return 0;
}
posted @   Doraemon_awa  阅读(43)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩