把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【学习笔记】prufer序列 洛谷P6086

题目链接

算法分析

(我似乎之前学过两遍prufer,但是我咕咕咕了,而且板子也不知道丢到哪里去了(难怪我这么菜

树 -> prufer 序列

算法流程:

  1. 找到树中编号最小的入度为1的结点(叶结点)
  2. 删去该结点,并将与该结点相邻结点的标号加入prufer序列
  3. 重复1,2操作n2次,直到图中只剩下两个结点。

实现方式:

  1. 每次暴力枚举编号最小的叶结点,进行操作。
  2. 堆优化。把度数变为1的点丢到堆里去,每次从堆里取结点,复杂度O(nlogn)
  3. 用指针。指针指向编号最小的叶结点,每次删掉之后,如果产生了新的叶结点,并且比指针指向的下一个更小,就直接删掉新产生的那个,否则指针自增找到下一个编号最小的叶结点,(其实没必要存图了)复杂度O(n)

一些性质

  1. 叶结点在prufer序列中不会出现
  2. 剩下的两个结点中,有一个结点的编号是最大的编号n
  3. 一个结点在prufer序列出现的次数为它度数1
  4. n个点的无向完全图的生成树计数,即n个点的有编号无根树计数:nn2
  5. n个点的有编号有根树的计数:nn2×n=nn1(无根树选一个节点作为根,所以×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);
}

prufer 序列 -> 树

算法流程:

(其实就是上述算法反过来而已

  1. 根据prufer序列的性质,可以算出每个点的度数。
  2. 找到一个编号最小的度数为1的结点。
  3. 将该结点与当前的prufer序列的点相连,然后同时减去这两个点的度数。
  4. 重复2,3操作n2次,只剩下两个结点。
  5. 将剩下的两个结点相连。

实现方式:

  1. 暴力枚举最小的度数为1的点。
  2. 堆优化。一样的,就不说了。
  3. 用指针。指针刚开始指向编号最小的度数为1的结点,每次连接之后,如果度数减掉之后产生了新的度数为1的结点,并且比指针指向的更小,就继续把新产生的结点搞掉,否则还是指针继续往下枚举找到下一个编号最小的度数为1的结点。
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;
}


posted @   Starlight_Glimmer  阅读(133)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
浏览器标题切换
浏览器标题切换end
点击右上角即可分享
微信分享提示