【學習筆記】prufer序列 洛谷P6086
阿新 • • 發佈:2020-11-24
目錄
演算法分析
(我似乎之前學過兩遍\(prufer\),但是我咕咕咕了,而且板子也不知道丟到哪裡去了(難怪我這麼菜
樹 -> prufer 序列
演算法流程:
- 找到樹中編號最小的入度為\(1\)的結點(葉結點)
- 刪去該結點,並將與該結點相鄰結點的標號加入\(prufer\)序列
- 重複\(1,2\)操作\(n-2\)次,直到圖中只剩下兩個結點。
實現方式:
- 每次暴力列舉編號最小的葉結點,進行操作。
- 堆優化。把度數變為\(1\)
- 用指標。指標指向編號最小的葉結點,每次刪掉之後,如果產生了新的葉結點,並且比指標指向的下一個更小,就直接刪掉新產生的那個,否則指標自增找到下一個編號最小的葉結點,(其實沒必要存圖了)複雜度\(O (n)\)
一些性質
- 葉結點在\(prufer\)序列中不會出現
- 剩下的兩個結點中,有一個結點的編號是最大的編號\(n\)
- 一個結點在\(prufer\)序列出現的次數為它度數\(-1\)
- \(n\)個點的無向完全圖的生成樹計數,即\(n\)個點的有編號無根樹計數:\(n^{n-2}\)
- \(n\)個點的有編號有根樹的計數:\(n^{n-2}\times n=n^{n-1}\)
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 序列 -> 樹
演算法流程:
(其實就是上述演算法反過來而已
- 根據\(prufer\)序列的性質,可以算出每個點的度數。
- 找到一個編號最小的度數為\(1\)的結點。
- 將該結點與當前的\(prufer\)序列的點相連,然後同時減去這兩個點的度數。
- 重複\(2,3\)操作\(n-2\)次,只剩下兩個結點。
- 將剩下的兩個結點相連。
實現方式:
- 暴力列舉最小的度數為\(1\)的點。
- 堆優化。一樣的,就不說了。
- 用指標。指標剛開始指向編號最小的度數為\(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;
}