【学习笔记】prufer序列 洛谷P6086
算法分析
(我似乎之前学过两遍,但是我咕咕咕了,而且板子也不知道丢到哪里去了(难怪我这么菜
树 -> prufer 序列
算法流程:
- 找到树中编号最小的入度为的结点(叶结点)
- 删去该结点,并将与该结点相邻结点的标号加入序列
- 重复操作次,直到图中只剩下两个结点。
实现方式:
- 每次暴力枚举编号最小的叶结点,进行操作。
- 堆优化。把度数变为的点丢到堆里去,每次从堆里取结点,复杂度。
- 用指针。指针指向编号最小的叶结点,每次删掉之后,如果产生了新的叶结点,并且比指针指向的下一个更小,就直接删掉新产生的那个,否则指针自增找到下一个编号最小的叶结点,(其实没必要存图了)复杂度
一些性质
- 叶结点在序列中不会出现
- 剩下的两个结点中,有一个结点的编号是最大的编号
- 一个结点在序列出现的次数为它度数
- 个点的无向完全图的生成树计数,即个点的有编号无根树计数:
- 个点的有编号有根树的计数:(无根树选一个节点作为根,所以
void T_to_P()
{
for(int i=1;i<=n-1;i++)
f[i]=rd(),d[f[i]]++;//儿子个数
for(int i=1,j=1/*指针*/;i<=n-2;i++,j++)
{
while(d[j]) j++;//找到下一个叶结点
p[i]=f[j];//将叶结点的爸爸记进prufer序列
d[f[j]]--;
while(i<=n-2&&d[p[i]]==0&&p[i]<j) p[i+1]=f[p[i]],d[p[i+1]]--,i++;//如果爸爸变成了叶结点并且比j小
}
LL ans=0;
for(int i=1;i<=n-2;i++)
ans=ans^(1ll*i*p[i]);
printf("%lld\n",ans);
}
prufer 序列 -> 树
算法流程:
(其实就是上述算法反过来而已
- 根据序列的性质,可以算出每个点的度数。
- 找到一个编号最小的度数为的结点。
- 将该结点与当前的序列的点相连,然后同时减去这两个点的度数。
- 重复操作次,只剩下两个结点。
- 将剩下的两个结点相连。
实现方式:
- 暴力枚举最小的度数为的点。
- 堆优化。一样的,就不说了。
- 用指针。指针刚开始指向编号最小的度数为的结点,每次连接之后,如果度数减掉之后产生了新的度数为的结点,并且比指针指向的更小,就继续把新产生的结点搞掉,否则还是指针继续往下枚举找到下一个编号最小的度数为的结点。
void P_to_T()
{
for(int i=1;i<=n-2;i++)
p[i]=rd(),d[p[i]]++;//数在prufer序列出现的次数是度数-1
p[n-1]=n;
//最后剩下的结点中一定有n 并且另一个点的编号小于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]]==0&&p[i]<j) f[p[i]]=p[i+1],d[p[i+1]]--,i++;
//如果爸爸(j的爸爸 是p[i])变成了度为1并且比j小 那么爸爸和下一个prufer序列里的数相连
}
LL ans=0;
for(int i=1;i<=n-1;i++)
ans=ans^(1ll*i*f[i]);
printf("%lld\n",ans);
}
►Code View
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define N 5000005
#define INF 0x3f3f3f3f
#define LL long long
int rd()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return f*x;
}
int n,opt;
int f[N],p[N],d[N];
void T_to_P()
{
for(int i=1;i<=n-1;i++)
f[i]=rd(),d[f[i]]++;//儿子个数
for(int i=1,j=1/*指针*/;i<=n-2;i++,j++)
{
while(d[j]) j++;//找到下一个叶结点
p[i]=f[j];//将叶结点的爸爸记进prufer序列
d[f[j]]--;
while(i<=n-2&&d[p[i]]==0&&p[i]<j) p[i+1]=f[p[i]],d[p[i+1]]--,i++;//如果爸爸变成了叶结点并且比j小
}
LL ans=0;
for(int i=1;i<=n-2;i++)
ans=ans^(1ll*i*p[i]);
printf("%lld\n",ans);
}
void P_to_T()
{
for(int i=1;i<=n-2;i++)
p[i]=rd(),d[p[i]]++;//数在prufer序列出现的次数是度数-1
p[n-1]=n;
//最后剩下的结点中一定有n 并且另一个点的编号小于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]]==0&&p[i]<j) f[p[i]]=p[i+1],d[p[i+1]]--,i++;
//如果爸爸(j的爸爸 是p[i])变成了度为1并且比j小 那么爸爸和下一个prufer序列里的数相连
}
LL ans=0;
for(int i=1;i<=n-1;i++)
ans=ans^(1ll*i*f[i]);
printf("%lld\n",ans);
}
int main()
{
n=rd(),opt=rd();
if(opt==1) T_to_P();
else P_to_T();
return 0;
}
转载请注明出处,有疑问欢迎探讨
博主邮箱 2775182058@qq.com
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现