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

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

题目链接

算法分析

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

树 -> prufer 序列

算法流程:

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

实现方式:

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

一些性质

  1. 叶结点在\(prufer\)序列中不会出现
  2. 剩下的两个结点中,有一个结点的编号是最大的编号\(n\)
  3. 一个结点在\(prufer\)序列出现的次数为它度数\(-1\)
  4. \(n\)个点的无向完全图的生成树计数,即\(n\)个点的有编号无根树计数:\(n^{n-2}\)
  5. \(n\)个点的有编号有根树的计数:\(n^{n-2}\times n=n^{n-1}\)(无根树选一个节点作为根,所以\(\times 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\)操作\(n-2\)次,只剩下两个结点。
  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 @ 2020-11-24 21:12  Starlight_Glimmer  阅读(130)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end