*【学习笔记】(21) Prufer 序列
Prufer 序列
Prufer 序列可以将一个带标号
Prufer 序列可以方便的解决一类树相关的计数问题,比如凯莱定理:
对树构造 Prufer 序列
每次选择一个编号最小的叶节点并删掉它,然后在序列中记录下它连接到的那个节点。
过程
重复
显然,使用堆可以做到
使用一个指针代替堆找最小值,可以做到
具体而言,指针指向编号最小的叶节点。每次删掉它之后,如果产生了新的叶节点且编号比指针指向的更小,则直接继续删掉,否则自增找到下一个编号最小的叶节点。
循环上述操作
- 如果
,则反正 往后扫描都会扫到它,于是不做操作; - 如果
,因为 原本就是编号最小的,而 比 还小,所以 就是当前编号最小的叶结点,优先删除。删除 继续这样的考虑直到没有更小的叶结点。
算法复杂度分析,发现每条边最多被访问一次(在删度数的时侯),而指针最多遍历每个结点一次,因此复杂度是
具体实现
void solve1(){ for(int i=1;i<n;++i) f[i]=read(),++d[f[i]]; for(int i=1,j=1;i<=n-2;++i,++j){ while(d[j]) ++j;p[i]=f[j]; while(i<=n-2&&!(--d[p[i]])&&p[i]<j) p[i+1]=f[p[i]],++i; } }
用 Prufer 序列构造树
根据 Prufer 序列的性质,我们可以得到原树上每个点的度数。
每次我们选择一个编号最小的度数为
同样地,显然,使用堆可以做到
类似地,使用一个指针代替堆找最小值,可以做到
具体而言,指针指向编号最小的度数为
具体实现
void solve2(){ for(int i=1;i<n-1;++i) p[i]=read(),++d[p[i]];p[n-1]=n; for(int i=1,j=1;i<n;++i,++j){ while(d[j]) ++j;f[j]=p[i]; while(i<n&&!(--d[p[i]])&&p[i]<j) f[p[i]]=p[i+1],++i; } }
通过这些过程其实可以理解,
prufer 序列的一些性质
1.与一棵有标号的无根树对应
2.某编号结点的入度为该点在prufer 序列中出现的次数
3.点数为
,有根树当然就
4.每个点度数为
的有标号无根树个数为
#include<bits/stdc++.h> #define N 5000005 #define int long long using namespace std; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int n,m,ans; int f[N],p[N],d[N]; void solve1(){ for(int i=1;i<n;++i) f[i]=read(),++d[f[i]]; for(int i=1,j=1;i<=n-2;++i,++j){ while(d[j]) ++j;p[i]=f[j]; while(i<=n-2&&!(--d[p[i]])&&p[i]<j) p[i+1]=f[p[i]],++i; } for(int i=1;i<=n-2;++i) ans^=i*p[i]; } void solve2(){ for(int i=1;i<n-1;++i) p[i]=read(),++d[p[i]];p[n-1]=n; for(int i=1,j=1;i<n;++i,++j){ while(d[j]) ++j;f[j]=p[i]; while(i<n&&!(--d[p[i]])&&p[i]<j) f[p[i]]=p[i+1],++i; } for(int i=1;i<n;++i) ans^=i*f[i]; } signed main(){ n=read(),m=read(); m==1?solve1():solve2(); printf("%lld\n",ans); return 0; }
#include<bits/stdc++.h> #define N 205 #define int long long using namespace std; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int n,ans=1,sum; int d[N],c[N][N]; signed main(){ n=read(); for(int i=1;i<=n;++i) d[i]=read(); if(n==1){ if(d[1]==0) printf("1\n"); else printf("0\n"); return 0; } for(int i=0;i<=n;++i){ c[i][0]=1; for(int j=1;j<=i;++j) c[i][j]=c[i-1][j]+c[i-1][j-1]; } for(int i=1;i<=n;++i){ if(!d[i]) return printf("0\n"),0; d[i]--; sum+=d[i]; } if(sum!=n-2) return printf("0\n"),0; sum=0; for(int i=1;i<=n;++i) ans*=c[n-2-sum][d[i]],sum+=d[i]; printf("%lld\n",ans); return 0; }
Cayley 公式 (Cayley's formula)
完全图
怎么证明?方法很多,但是用 Prufer 序列证是很简单的。任意一个长度为
图连通方案数
本文作者:南风未起
本文链接:https://www.cnblogs.com/jiangchen4122/p/17464604.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步