prufer 序列浅谈
prufer 序列完成了从一棵大小为 \(n\) 的无根树到长度为 \(n-2\) 的序列的双射,下面简述其构造过程:
从一棵无根树到 prufer 序列:
我们找到其编号最小的叶子,然后删掉叶子,把其父亲加入队列。重复操作,直到整棵树剩下两个节点。
\(O(n\log n)\) 是显然可以做的,我们考虑如何 \(O(n)\)。
用一个指针,指向当前编号最小的叶子节点,然后删掉他,如果其父亲变成了叶子,并且编号比它小,我们就删掉其父亲并重复操作,否则我们往后推指针,找到下一个叶子。
从 prufer 序列到一棵无根树
容易发现,每个节点的度数是在 prufer 序列中的出现次数加 \(1\),考虑我们现在知道每个点的度数,然后我们找到编号最小的叶子,然后删掉它,如果其父亲也变成了叶子,并且编号较小,我们重复这个操作,否则的话,我们考虑往后推指针,找到下一个叶子。最后会剩下两个点,其中一个节点是 \(n\),我们只需要在 prufer 序列最后加入 \(n\) 就可以避免特判。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define int long long
#define ull unsigned long long
#define mset(a,b) memset(a,b,sizeof(a))
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define N 5000100
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int n,m,d[N];
namespace Subtask1{
int k=INF,fa[N],p[N];
inline void Solve(){
for(int i=1;i<=n-1;i++){
int x;read(x);
d[x]++;d[i]++;fa[i]=x;
}
for(int i=1,j=1;i<=n-2;i++,j++){
while(d[j]!=1) j++;p[i]=fa[j];d[j]--;d[fa[j]]--;
while(i<=n-2&&d[p[i]]==1&&p[i]<j){p[i+1]=fa[p[i]];d[p[i]]--;d[fa[p[i]]]--;i++;}
}
int ans=0;
for(int i=1;i<=n-2;i++) ans=(ans^(i*p[i]));
printf("%lld\n",ans);
}
}
namespace Subtask2{
int fa[N],p[N],d[N];
inline void Solve(){
for(int i=1;i<=n-2;i++) read(p[i]),d[p[i]]++;
p[n-1]=n;
for(int i=1;i<=n;i++) d[i]++;
for(int i=1,j=1;i<=n-1;i++,j++){
while(d[j]!=1) j++;fa[j]=p[i];d[j]--;d[p[i]]--;
while(i<=n-1&&d[p[i]]==1&&p[i]<j){fa[p[i]]=p[i+1];d[p[i]]--;d[fa[p[i]]]--;i++;}
}
int ans=0;
for(int i=1;i<=n-1;i++) ans=(ans^(i*fa[i]));
printf("%lld\n",ans);
}
}
signed main(){
read(n);read(m);
if(m==1) Subtask1::Solve();else Subtask2::Solve();
return 0;
}
不过更需要注意的是,上面我们定义的叶子是不严谨的。
注意,prufer 序列是有标号无根树与长度为 \(n-1\) 序列的一种双射,由此可以知道有标号无根树的个数为 \(n^{n-2}\) 个。而我们所谓的叶子,实际上就是度数为 \(1\) 的点。而洛谷上之所以定义了父亲等,是因为我们以 \(n\) 为根,而其一定是不会被删掉的。
经过一系列的化简,我么你可以得到比 Cayley 定理(上面那个有标号无根树个数的定理)更强的定理,如果给定了 \(k\) 个连通块,总共连了 \(n\) 个点,把这些连通块练成树的方案数应该为:
其中 \(s_i\) 表示第 \(i\) 个连通块的大小。
我们考虑如何证明,首先不关注这些连通块内部的点,设完边后第 \(i\) 个连通块的度数为 \(d_i\),那么我们有:
上面这个式子是因为我们度数为 \(x\) 的点在 prufer 序列中的出现次数为 \(x-1\)。
然后关注到上面这个式子每一个方案都对应着一棵树,而每个连通块都需要选出一个点来进行链接,由此我们可以得到总的方案数应该为:
根据多元二项式定理进行化简,我们就可以得到上面给定的那个式子。