1. 程式人生 > 實用技巧 >【學習筆記】prufer序列 洛谷P6086

【學習筆記】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;
}