算法分析
(我似乎之前学过两遍(prufer),但是我咕咕咕了,而且板子也不知道丢到哪里去了(难怪我这么菜
树 -> prufer 序列
算法流程:
- 找到树中编号最小的入度为(1)的结点(叶结点)
- 删去该结点,并将与该结点相邻结点的标号加入(prufer)序列
- 重复(1,2)操作(n-2)次,直到图中只剩下两个结点。
实现方式:
- 每次暴力枚举编号最小的叶结点,进行操作。
- 堆优化。把度数变为(1)的点丢到堆里去,每次从堆里取结点,复杂度(O(nlogn))。
- 用指针。指针指向编号最小的叶结点,每次删掉之后,如果产生了新的叶结点,并且比指针指向的下一个更小,就直接删掉新产生的那个,否则指针自增找到下一个编号最小的叶结点,(其实没必要存图了)复杂度(O (n))
一些性质
- 叶结点在(prufer)序列中不会出现
- 剩下的两个结点中,有一个结点的编号是最大的编号(n)
- 一个结点在(prufer)序列出现的次数为它度数(-1)
- (n)个点的无向完全图的生成树计数,即(n)个点的有编号无根树计数:(n^{n-2})
- (n)个点的有编号有根树的计数:(n^{n-2} imes n=n^{n-1})(无根树选一个节点作为根,所以( imes 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
",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
",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
",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
",ans);
}
int main()
{
n=rd(),opt=rd();
if(opt==1) T_to_P();
else P_to_T();
return 0;
}