随机造树方法
1.随机父亲(深度 \(\Theta(\log n)\))
树以 \(1\) 为根,则 \(\forall i\in[2,n]\),在 \([1,i)\) 中随机选择一个点作为自身父亲:
namespace RandomTreeGenerator{
mt19937 rnd(time(0));
void generateRandomTree(int n){
int x;for(int i=2;i<=n;++i) x=rnd()%(i-1)+1,Edge(x,i);
}
}
树的结点个数 \(n\) 与造出的树的平均深度 \(\times (5\times 10^3)\) 的关系如图中黑线:
图中红线为 \(y=(5\times 10^3)\times 1.6\log x\) 的函数图像,可以看出在 \(n\le 5\times 10^5\) 时这种造树方法得到树的平均深度约为 \(1.6\log n\)。
2.Prüfer 序列(深度\(\Theta(\sqrt{n})\))
众所周知,一个长度为 \(n-2\) 的 Prüfer
序列唯一对应着一棵无根树,所以我们可以随机生成一个 Prüfer
序列,将其按模板题的方法转化成树。
namespace RandomTreeGenerator{
mt19937 rnd(time(0));
int d[N],p[N];
void generateRandomTree(int n){
for(int i=1;i<=n;++i) d[i]=1;
for(int i=1;i<=n-2;++i) ++d[p[i]=rnd()%n+1];
int now,pre;for(int i=1;i<=n;++i) if(d[i]==1){pre=now=i;break;}
for(int i=1;i<=n-2;++i){
int f=p[i];Edge(now,f);
if(--d[f]==1&&f<pre) now=f;
else{while(d[++pre]!=1);now=pre;}
}
Edge(now,n);
}
}
树的结点个数 \(n\) 与造出的树以 \(1\) 为根时的平均深度 \(\times 100\) 的关系如图中黑线:
图中红线为 \(y=100\times 2.5\sqrt{x}\) 的函数图像,可以看出在 \(n\le 5\times 10^5\) 时这种造树方法得到树的平均深度约为 \(2.5\sqrt{n}\)。
3.左偏树(硬核)
指定一个 \([1,n]\) 中的常数 \(k\),把 \([1,k]\) 按顺序排成一条 \(1\) 在最上方的链,然后 \(\forall i\in (k+1,n]\),随机选择 \([i-k,i)\) 中的一个结点作为父亲。
造出树的深度下界为 \(\Omega\left(k+\frac{n}{k}\right)\)(本人口胡,不一定准确,但经过实际测试大概是这样的)。感性理解,造出链后要使深度尽量小,最优方案便是把所有结点平均接到链中每个点的下方,像这样:
此时树的深度为 \(\Theta\left(k+\frac{n}{k}\right)\),所以随机情况下树的深度为 \(\Omega\left(k+\frac{n}{k}\right)\)。
namespace RandomTreeGenerator{
#include <cassert>
mt19937 rnd(time(0));
const int k=3;
void generateRandomTree(int n){
assert(k<=n);
for(int i=2;i<=k;++i) Edge(i,i-1);
for(int i=k+1;i<=n;++i) Edge(i,rnd()%k+i-k);
}
}