1. 程式人生 > 其它 >2419. prufer序列

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;
}