Prüfer 序列 学习笔记
前言
- msjing一下午学了 \(4\) 个东西,现在非常有精神
这个紫题和我上午刷的真的是同一个难度吗
——msjing
Prüfer简介及操作
- 这个玩意看起来好吊的样子,那这究竟是个啥呢
Prüfer可以将一个带标号 \(n\) 个结点的树用 \([1,n]\) 中的 \(n-2\) 个整数表示.你也可以把它理解为完全图的生成树与数列之间的双射,常用组合计数问题中。
——oiwiki
计算序列
- 这样的话一个有标无根树就可以表示了,具体怎么操作呢
- 每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点.重复 \(n-2\) 次后就只剩下两个结点,算法结束。
- 我们来徒手模拟一下
c这个CS Academy怎么加载这么慢,算了画吧,欸你怎么加载好了

艹浏览器突然无了图啥的全没了请忽视字体和图大小不一和 HZ 保密画质- 我们有了这个序列之后,我们就可以还原这块树了,怎么还原呢
树的还原
- 性质:
- 每个结点在序列中出现的次数是其度数 \(-1\)。
- 没有出现的是叶子结点
- 有了这个,我们就可以依据数列还原了
- 我们选一个度数为 \(1\) 的编号最小的点,与当前枚举到的序列的点连接,然后同时减掉两个点的度,不断重复,直到最后剩下两个度数为 \(1\) 的点,把它们连上就行了,度用桶维护,给图(图中是先把 \(5\) 和 \(7\) 连上了)


- 我们可以结合图与表清晰理解
线性构造序列和还原树
- 构造的话,优先队列的堆做法比较显然,e,不准备胡了
- 但是我们需要线性搞,所以我们来胡一下
- 先胡树构建序列
瞎胡时间
- 遍历找最小叶子节点,找到后把它父亲加入序列,父亲度 \(-1\)
- 为了方便表述,把上面说的父亲设为 \(f\),遍历找的叶子节点的下标设为 \(i\)
- 如果 \(f\) 成为了叶子结点,判断其与 \(i\) 的大小关系,如果 \(f<i\),就直接加入,并把 \(f\) 的父亲度 \(-1\),然后重复上述,否则就跳过就行
- 由于每个点只会被遍历一次,所以是 \(O(n)\) 的
貌似胡对了- 序列重建树差不多,msjing不打算胡了,咕咕咕
- 代码msjing是 \(rand\) 了一个贺的,给吧
点击查看代码
#include<bits/stdc++.h>
#define pb push_back
#define int long long
using namespace std;
constexpr int maxn=5e6+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,f[maxn],p[maxn],d[maxn];
struct pru
{
void ttop()
{
for (int i=1,j=1;i<=n-2;i++,j++)
{
while (d[j]) j++;
p[i]=f[j];
d[f[j]]--;
while (i<=n-2 && !d[p[i]] && p[i]<j) p[i+1]=f[p[i]],d[p[i+1]]--,i++;
}
int res=0;
for (int i=1;i<=n-2;i++) res^=i*p[i];
printf("%lld\n",res);
}
void ptot()
{
p[n-1]=n;
for (int i=1,j=1;i<=n-1;i++,j++)
{
while (d[j]) j++;
f[j]=p[i];
d[f[j]]--;
while (i<=n-1 && !d[p[i]] && p[i]<j) f[p[i]]=p[i+1],d[p[i+1]]--,i++;
}
int res=0;
for (int i=1;i<=n-1;i++) res^=i*f[i];
printf("%lld\n",res);
}
}P;
signed main()
{
n=read();
int op=read();
if (op == 1)
{
for (int i=1;i<=n-1;i++) f[i]=read(),d[f[i]]++;
P.ttop();
}
else
{
for (int i=1;i<=n-2;i++) p[i]=read(),d[p[i]]++;
P.ptot();
}
return 0;
}
性质
- e,这个会随着刷题的增加慢慢补充
- 对于给定度数为 \(d\) 的一棵无根树共有 \({(n-2)!} \over {\prod^{n}_{i=1}{(d_i - 1)!}}\) 种情况
例题
P2290 [HNOI2004] 树的计数
- \(Python\) 创似所有高精!!!
- e,给出所有点的度,让求方案数,其实是比较裸的 \(Prufer\),柿子就是上面性质的第一个(好像只有一个 on 26.7.20),套柿子搞一下就行了
- 巴特!这个东西会爆 \(long\) \(long\),所以要上高精,但是msjing太咕了于是直接用 \(Python\) 创过去了
Python 创创创!
n=(int)(input())
if n == 1:
x=(int)(input())
if x == 0: print(1)
else: print(0)
else:
f=[0]*(n+10)
f[0]=1
for i in range(1,n+1):
f[i]=f[i-1]*i
ans=f[n-2]
cnt=0
s=input().split()
for i in range(n):
x=(int)(s[i])
if x == 0:
print(0);exit()
ans//=f[x-1];cnt+=x-1
if cnt == n-2:print(ans)
else:print(0)
后话
- msjing学的非常草率,所以就现这样吧(·W·)

浙公网安备 33010602011771号