2419. prufer序列
题目链接
2419. prufer序列
本题需要你实现prufer序列与无根树之间的相互转化。
假设本题涉及的无根树共有 \(n\) 个节点,编号 \(1 \sim n\)。
为了更加简单明了的描述无根树的结构,我们不妨在输入和输出时将该无根树描述为一个以 \(n\) 号节点为根的有根树。
这样就可以设这棵无根树的父亲序列为 \(f_1,f_2,…,f_{n-1}\),其中 \(f_i\) 表示将该树看作以 \(n\) 号节点为根的有根树时,\(i\) 号节点的父节点编号。
同时,设这棵无根树的prufer序列为 \(p_1,p_2,…,p_{n-2}\)。
现在,给定一棵由 \(n\) 个节点构成的无根树,以及该无根树的一个序列(父亲序列或prufer序列),请你求出另一个序列。
输入格式
输入共两行。
第一行包含两个整数 \(n,m\),表示无根树的节点数以及给定序列类型。
如果 \(m = 1\),则第二行包含 \(n-1\) 个整数,表示给定序列为父亲序列。
如果 \(m = 2\),则第二行包含 \(n-2\) 个整数,表示给定序列为prufer序列。
输出格式
共一行,输出另一个序列,整数之间用单个空格隔开。
数据范围
\(2 \le n \le 10^5\)
输入样例1:
6 1
3 5 4 5 6
输出样例1:
3 5 4 5
输入样例2:
6 2
3 5 4 5
输出样例2:
3 5 4 5 6
解题思路
prufer编码
prufer编码主要作用即将一棵无根树转化为一个序列(即prufer序列),另外prufer序列也可以反过来转化为一棵树,即prufer序列和树之间是一一对应的,常用来解决一些证明问题,如凯莱定理等
证明凯莱定理(一个无向完全图有 \(n^{n-2}\) 棵生成树):由于prufer序列和树之间是一一对应的关系,证明有多少棵不同的生成树即证明有多少种prufer序列,显然,prufer序列共有 \(n-2\) 项,其范围为 \(1\sim n\),故其种类数为 \(n^{n-2}\)
prufer编码的流程:假定 \(n\) 号节点为根,找到除根外度数最小的节点,在删除该节点之前,将其父节点输出,重复该流程,直到最后只剩下两个节点,即prufer序列只有 \(n-2\) 个元素,因为prufer序列最多 \(n-1\) 个元素,而最后一个元素一定为 \(n\),所以这个元素可以省略,输出的元素即为prufer序列
\(\color{red}{如何将一棵树线性时间内转化为prufer序列?}\)
假定当前出度为 \(0\) 且编号最小的节点为 \(j\),则输出 \(f[j]\),删除 \(j\) 之后,出度为 \(0\) 的节点至多只会增加一个,即 \(f[j]\),判断删除 \(j\) 之后 \(f[j]\) 的出度是否为 \(0\),如果 \(f[j]\) 的出度为 \(0\) 且 \(f[j]<j\) 说明 \(f[j]\) 是当前出度为 \(0\) 且编号最小的节点,递归输出这样的父节点即可,否则说明这样的 \(j\) 只会更大,即 \(j\) 只会增加,这样即可线性时间内将一颗树转化为prufer序列
\(\color{red}{如何将一个prufer序列转化为一棵树?}\)
先将 \(n\) 这个节点加入到prufer序列中,不难发现,prufer序列中某个数出现的次数即为该数在树中的儿子节点的数量,从 \(1\) 开始找到儿子数量为 \(0\) 且编号最小的节点 \(j\),其父节点即为当前遍历的prufer序列的元素,将该元素从prufer序列中删去,因为删除该元素后儿子数量为 \(0\) 的节点数量至多直接增加一个,如果该元素的儿子数量为 \(0\) 且编号小于 \(j\),说明当前节点即为儿子数量为 \(0\) 且编号最小的节点,递归处理即可,这样的 \(j\) 同样也是递增的,故可以在线性时间内将一个prufer序列转化为一棵树
- 时间复杂度:\(O(n)\)
代码
// Problem: prufer序列
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2421/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=1e5+5;
int n,m;
int f[N],p[N],d[N];
void tree2prufer()
{
for(int i=1;i<n;i++)
{
scanf("%d",&f[i]);
d[f[i]]++;
}
for(int i=0,j=1;i<n-2;j++)
{
while(d[j])j++;
p[i++]=f[j];
while(i<n-2&&--d[p[i-1]]==0&&p[i-1]<j)p[i++]=f[p[i-1]];
}
for(int i=0;i<n-2;i++)printf("%d ",p[i]);
}
void prufer2tree()
{
for(int i=1;i<=n-2;i++)
{
scanf("%d",&p[i]);
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]]==0&&p[i]<j)f[p[i]]=p[i+1],i++;
}
for(int i=1;i<n;i++)printf("%d ",f[i]);
}
int main()
{
scanf("%d%d",&n,&m);
if(m==1)tree2prufer();
else
prufer2tree();
return 0;
}