【学习笔记】Prüfer 序列
定义
Prüfer 序列能够将一个有
如何对于一棵树建立它的 Prüfer 序列?每次选择该树最小的叶子结点并删掉它,然后在序列的末尾把它所连接的节点加入,知道只剩
实现
首先考虑暴力实现,显然有一个
然后考虑优化,不难发现用堆即可优化到
但是,Prüfer 序列是可以线性构造的,具体流程如下:
维护一个指针
p ,初始时p 指向编号最小的叶子结点,同时我们维护每个节点的出度cnt 以及每个节点的父亲,每次删除一个节点u 时,将它的父亲fa 加入到序列中,同时cnt_{fa}=cnt_{fa}-1 ,然后判断若cnt_{fa}=0 且fa<u 则u=fa 然后继续,否则就u 递加,可以证明此方法正确。
不难发现编号最大的节点一定不会被删除,因为一个点数多于
解决的问题
Prüfer 序列可以解决许多与生成树计数有关的题目,每有一个 Prüfer 序列就有一种生成树的方案。
例题
【模板】Prufer 序列
板子,按照上面的线性建 Prüfer 序列的方法即可,但是还需要将 Prüfer 序列转化为树,方法其实与建立 Prüfer 序列差不多,故不在赘述。
由于题目说以
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+5;
int fa[N],cnt[N],p[N];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int n,op;
cin>>n>>op;
long long ans=0;
if(op==1)
{
for(int i=1;i<n;i++)cin>>fa[i],cnt[fa[i]]++;
for(int i=1,j=1;i<=n-2;i++,j++)
{
while(cnt[j]!=0)j++;
p[i]=fa[j];
while(i<n-2&&--cnt[p[i]]==0&&p[i]<j)p[i+1]=fa[p[i]],i++;
}
for(int i=1;i<=n-2;i++)ans^=1ll*i*p[i];
}
else
{
for(int i=1;i<=n-2;i++)cin>>p[i],cnt[p[i]]++;
p[n-1]=n;
for(int i=1,j=1;i<n;i++,j++)
{
while(cnt[j]!=0)j++;
fa[j]=p[i];
while(i<n-1&&--cnt[p[i]]==0&&p[i]<j)fa[p[i]]=p[i+1],i++;
}
for(int i=1;i<n;i++)ans^=1ll*i*fa[i];
}
cout<<ans;
return 0;
}
[HNOI2004] 树的计数
根据 Prüfer 序列的性质,每个 Prüfer 序列对应一种生成树的方案,故考虑有几种 Prüfer 序列。
题目给定每个节点的度数,即规定了每个节点在 Prüfer 序列出现的次数,算个多重组合数即为答案。设答案为
上式中
需要注意的是,会出现无解的情况,所有情况如下:
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=205;
int cnt[N];
void solve(int n,int val)
{
for(int i=1;i<=n;i++)
{
int tmp=i;
for(int j=2;j*j<=tmp;j++)
while(tmp%j==0)cnt[j]+=val,tmp/=j;
if(tmp>1)cnt[tmp]+=val;
}
}
int a[N];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int n,sum=0,c=0;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i],c+=(a[i]==1?1:0);
if(sum!=2*(n-1)||n!=1&&c<2||n==1&&a[1]!=0)
{
cout<<0;
return 0;
}
solve(n-2,1);
for(int i=1;i<=n;i++)solve(a[i]-1,-1);
long long ans=1;
for(int i=1;i<=150;i++)
for(int j=1;j<=cnt[i];j++)
ans*=i;
cout<<ans;
return 0;
}