Prüfer 序列学习笔记
Prüfer 序列是一种将带标号的树用一个唯一的整数序列表示的方法。
Prüfer 序列的构造
每次选择树上标号最小的叶子节点,把它连接的节点编号加入序列中,并且把它删掉。这样重复 \(n-2\) 次后剩下两个相连节点,生成的序列就是 Prüfer 序列。
使用堆的 \(O(n\log n)\) 构造方法是显然的。考虑线性构造过程。
记录指针 \(now\) 指向当前编号最小的叶子节点,设 \(l\) 为当前将要删掉的叶子,初始时 \(l=now\)。
删除 \(l\) 之后,
- 如果它连接的节点(设为 \(tmp\))变成新的最小叶子,那么令 \(l=tmp\);
- 否则让 \(now\) 自增,直到找到新的最小叶子。
显然这样做是对的,而且时间复杂度是 \(O(n)\) 的。
用 Prüfer 还原树也可以用类似以上的方法用 \(O(n)\) 的时间实现。
int main()
{
n=read();op=read();
if(op==1)
{
for(int i=1;i<n;i++)fa[i]=read(),++ind[fa[i]],++ind[i];
for(int i=1;i<=n;i++)
{
if(ind[i]==1)
{
now=i;break;
}
}
l=now;
for(int i=1;i<=n-2;i++)
{
int tmp=fa[l];--ind[l];ans^=1ll*i*fa[l];
if(--ind[tmp]==1&&tmp<now)
{
l=tmp;
}
else
{
++now;
while(ind[now]!=1)++now;
l=now;
}
}
}
else
{
for(int i=1;i<=n-2;i++)p[i]=read(),++ind[p[i]];
for(int i=1;i<=n;i++)++ind[i];
for(int i=1;i<=n;i++)
{
if(ind[i]==1)
{
now=i;break;
}
}
l=now;
for(int i=1;i<=n-2;i++)
{
fa[l]=p[i];--ind[l];int tmp=fa[l];
if(--ind[tmp]==1&&tmp<now)
{
l=tmp;
}
else
{
++now;
while(ind[now]!=1)++now;
l=now;
}
}
fa[l]=n;
for(int i=1;i<n;i++)ans^=1ll*i*fa[i];
}
write(ans);
return 0;
}
Prüfer 序列的性质与应用
- 每个节点在序列中出现的次数是其度数 \(-1\)。
- 每个长度为 \(n-2\),值域为 \([1,n]\) 的序列和一棵有 \(n\) 个点的无根树一一对应。
Cayley 公式
\(n\) 个点的完全图有 \(n^{n-2}\) 棵生成树。
图连通方案数
一个 \(n\) 个点的带标号无向图被分成了 \(k\) 个连通块,每个连通块有 \(s_i\) 个节点,求加最少的边使得图连通的方案数。
答案是 \(n^{k-2}\sdot \prod s_i\)。证明.
例题
因为有 Prüfer 序列与树一一对应的性质,只需要对 Prüfer 序列计数。非常简单,但是需要写高精度。
BZOJ4766 文艺计算姬
给定一张有 \(n\) 个左部点,\(m\) 个右部点的带标号完全二分图,计算其生成树个数。
考虑对 Prüfer 序列计数。最后两个点一定一个是左部点一个是右部点,剩下的点在序列中一定是左部点出现了 \(m-1\) 次,右部点出现了 \(n-1\) 次,答案就是 \(n^{m-1}m^{n-1}\)。
考虑求有 \(k\) 个左部点,\(n-k\) 个右部点的完全二分图的生成树个数。需要用上面那个结论。答案是 \(k^{n-k-1}n-k^{k-1}\binom{n-1}{k-1}\)。
显然是先把所有度数的价值算出来然后做一个背包之类的。还要记录方案。
for(int i=1;i<=n-2;i++)
{
for(int j=0;j<i;j++)
if(g[i]<g[j]+f[i-j+1]-f[1])
{
g[i]=g[j]+f[i-j+1]-f[1];
from[i]=j;
}
}
首先枚举每个节点实际度数 \(d_i\),再计算选孔方案。
有一个式子:
一顿化简之后得到:
然后就做完了。
浙公网安备 33010602011771号