Prüfer 序列学习笔记
Prüfer 序列学习笔记
知识点#
前言#
Prüfer 序列是为了证明 Cayley 公式而被发明出来的,即一个
Prüfer 序列可以将一个
Prüfer 序列的构造#
每次找到编号最小的一个叶子节点,将与其相连的节点加入序列,并将其在树上删除,直到树上只有两个节点。
Prüfer 序列具有以下两个显然的性质:
- 树中最后剩下的两个点中,一定存在一个节点是编号最大的点
- 每个节点在序列中出现的次数为该节点的度数
。
Prüfer 序列的重构树#
下面证明 Prüfer 序列可以唯一表示一棵树。
依次考虑 Prüfer 序列
存在一个更为优秀的线性做法。
令指针
多次进行上面的操作就可以得到重构树。
例题#
CF156D Clues#
小 D 最近在幼儿园学习了图论知识。
他知道对于一个个点 条边的带标号无向图 ,若形成了 个连通块,最少增加 条边就可以使得图 连通。
但是小 D 突发奇想,他想知道增加条边使得整个图连通的方案数对他喜欢的模数 取模的结果。
对于全部数据,, 。
设增加
对于给定序列
其中,
对于给定序列
枚举序列
令
考虑多元二项式定理:
可以将原式变为
点击查看代码
#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#
给定一个使得其总和为
的非负整数序列 。
对于带编号从到 的顶点,假设 是根,我们将其点度数定义为 。
我们称满足以下条件的 根向树 为好树:
点 的出度是 。
此外,对于好树的顶点,定义 为以 为根的子树中(包括 )顶点编号的最小值。我们将满足 的顶点称为好顶点。
求好树中所有好顶点的总数,将其对取模后的余数。
对于全部数据,, , , 。
点击查看代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现