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个节点的完全图,其无根树共有\(n^{{n-2}}\)种情况(Cayley公式)
2.对于一个n个节点的完全图,其无根树共有\(n^{{n-1}}\)种情况(因为每个点都能为根)
3.已知每个点的度数,其生成树共有\(\frac{(n-2)!}{\prod_{1}^{n}(deg[i]-1)!}\)种情况。

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);
}
posted @ 2024-04-17 16:41  Captainfly19  阅读(3)  评论(0编辑  收藏  举报