浅谈Prufer序列
是什么
\(Prufer\) 数列是无根树的一种数列。在组合数学中,\(Prufer\) 数列由有一个对于顶点标过号的树转化来的数列,点数为 \(n\) 的树转化来的 \(Prufer\) 数列长度为 \(n-2\)。
对于一棵确定的无根树,对应着唯一确定的 \(Prufer\) 序列,运用一一对应的组合技巧,往往能巧妙做出许多树形组合问题。
构造方法
无根树转化为 \(Prufer\) 序列
- 找到编号最小的度数为 \(1\) 的点
- 删除该节点并在序列中添加与该节点相连的节点的编号
- 重复 \(1,2\) 操作,直到整棵树只剩下两个节点
\(Prufer\) 序列转化为无根树
- 每次取出 \(Prufer\) 序列中最前面的元素 \(u\)
- 在点集中找到编号最小的没有在 \(prufer\) 序列中出现的元素 \(v\)
- 给 \(u,v\) 连边然后分别删除
- 最后在点集中剩下两个节点,给它们连边
例如,对于 \(prufer\) 序列 \(3,5,1,3\)
连边顺序为:
\(2,3\)
\(5,4\)
\(1,5\)
\(3,1\)
\(3,6\)
以上两种操作都可以线性,原理可以看这里。
性质
- \(Prufer\) 序列中某个编号出现的次数就等于这个编号的节点在无根树中的度数 \(-1\)
- 一棵 \(n\) 个节点的无根树唯一地对应了一个长度为 \(n-2\) 的数列,数列中的每个数都在 \(1\) 到 \(n\) 的范围内。
- \(n\) 个点的无向完全图的生成树的计数:\(n^{n−2}\),即 \(n\) 个点的有标号无根树的计数
- \(n\) 个节点的度依次为 \(d_1,d_2,…,d_n\) 的无根树共有 \(\frac{(n−2)!}{\prod_{i=1}^n (d_i−1)!}\)个,因为此时 \(Prufer\) 编码中的数字 \(i\) 恰好出现 \(d_i−1\) 次,\((n−2)!\) 是总排列数
- \(n\) 个点的有标号有根树的计数:\(n^{n-2}\times n=n^{n-1}\)
题
Luogu P6086 【模板】Prufer 序列
\(Prufer\) 的线性互相转化。
#include<bits/stdc++.h>
using namespace std;
template <class T>
inline T read(){
T r=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
return f?-r:r;
}
const int N=5e6+5;
int n,m,a[N],d[N],fa[N];
long long ans;
void TtoP(){
for(int i=1;i<n;++i) fa[i]=read<int>(),++d[fa[i]];
for(int i=1,j=1;i<n-1;++i,++j){
while(d[j]) ++j;
a[i]=fa[j];
while(i<n-1&&!--d[a[i]]&&a[i]<j) a[i+1]=fa[a[i]],++i;
}
for(int i=1;i<n-1;++i) ans^=1ll*i*a[i];
}
void PtoT(){
for(int i=1;i<n-1;++i) a[i]=read<int>(),++d[a[i]]; a[n-1]=n;
for(int i=1,j=1;i<n;++i,++j){
while(d[j]) ++j;
fa[j]=a[i];
while(i<n&&!--d[a[i]]&&a[i]<j) fa[a[i]]=a[i+1],++i;
}
for(int i=1;i<n;++i) ans^=1ll*i*fa[i];
}
int main(){
n=read<int>();
if(read<int>()==1) TtoP();
else PtoT();
printf("%lld",ans);
return 0;
}
Luogu P2290 [HNOI2004]树的计数
性质4,开 __int128
套上去完事。
#include<bits/stdc++.h>
using namespace std;
template <class T>
inline T read(){
T r=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
return f?-r:r;
}
const int N=155;
int n,d[N],sum;
__int128 tmp=1;
int main(){
n=read<int>();
if(n==1) return puts(read<int>()?"0":"1"),0;
for(int i=1;i<n-1;++i) tmp*=i;
for(int i=1;i<=n;++i){
d[i]=read<int>();
if(!d[i]) return puts("0"),0;
sum+=d[i]-1;
for(int j=1;j<d[i];++j) tmp/=j;
}
printf("%lld",sum==n-2?(long long)tmp:0);
return 0;
}