Prufer序列
Prufer序列通常在图的计数问题中比较常用。
Prufer序列的构造方法:(图片源自oiwiki)
具体操作步骤:先找到叶子结点中编号最小的节点,然后删除。在Prufer序列中的元素就是每次删除的节点的父节点。由于最后操作必然会剩下两个节点,两个节点都是叶子结点,于是操作完毕,最终构造出的Prufer序列有n-2个元素。
Prufer序列有以下几个性质:
1.假设节点u的度数为deg[u],那么节点u在Prufer序列中的出现次数为deg[u]-1。
2.在构造完原树之后会剩下2个节点,其中一个必然是编号最大的节点。
3.一个Prufer序列对应的树唯一,一颗树的Prufer序列唯一。
一些由基本性质扩展出来的性质:
1.对于一个n个节点的完全图,其无根树共有
2.对于一个n个节点的完全图,其无根树共有
3.已知每个点的度数,其生成树共有
P6086 【模板】Prufer 序列:https://www.luogu.com.cn/problem/P6086
模版题。
1.构造Prufer序列的线性做法的大致思路:
从1到n-1遍历每个点,当发现当前点是叶子结点(度数为1),进行操作。首先将其父节点加入Prufer序列中,将当前节点删除并将父节点的度数-1.如果父节点因当前删除操作而成为叶子结点,我们比较父节点和当前节点的大小。若父节点比当前节点更小则下一步操作必删父节点,继续递归操作即可。否则继续按照顺序遍历节点,直到找到叶子结点再进行删点操作。
void del(int x,int y)//x是当前点的编号,y是当前的编号大小上限
{
pru[++tot]=x;//prufer 序列
if(--deg[fa[x]]==1&&fa[x]<y) add(fa[x],y);//递归加点
}
for(int i=1;i<=n;i++)
{
if(deg[i]==1) del(i,i);
}
2.从Prufer序列还原树的操作 线性做法大致思路:
我们知道每个点在Prufer序列中的的出现次数是deg[u]-1,那么通过每个点的出现次数可以知道度数,知道度数了之后可以判断是否为叶子结点。
我们遍历Prufer序列中的每一个元素,统计其出现次数。再从1到n-1遍历每个点,当发现其出现次数为0说明是叶子结点,那么按照Prufer序列中从前往后的顺序依次还原其父节点。假如父节点的编号比当前节点更小那么下一个必定还原父节点,实际上就是把生成Prufer序列的操作反过来了。
void add(int x,int y)
{
fa[x]=pru[++tot];
if(--cnt[fa[x]]==0&&fa[x]<y) add(fa[x],y);
}
for(int i=1;i<=n;i++)
{
if(cnt[i]==0) add(i,i);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效