参考博客https://www.cnblogs.com/dirge/p/5503289.html

(1)prufer数列是一种无根树的编码表示,类似于hash。

一棵n个节点带编号的无根树,对应唯一串长度为n-2的prufer编码。所以一个n阶完全图的生成树个数就是

首先定义无根树中度数为1的节点是叶子节点。

找到编号最小的叶子并删除,序列中添加与之相连的节点编号,重复执行直到只剩下2个节点。

(2)prufer序列转化为无根树。

我们设点集为{1,2...n}。然后我们每次找到点集中没有出现在prufer序列中的最小的点(这一定是这个时刻删除的叶子节点),然后再取出prufer序列中的第一个元素,两个点建边,在将两个点在分别删除。

重要性质:prufer序列中某个编号出现的次数就是这个编号节点的度数-1。

很多时候,无根树树的问题都可以转化为求解prufer序列的问题,从而大大简化了问题。

下面提供了三道例题。

例题:

BSOJ 2503 -- 【HNOI2004】树的计数

Description

  一个有n个结点的树,设它的结点分别为v1, v2, …, vn,已知第i个结点vi的度数为di,问满足这样的条件的不同的树有多少棵。 

  给定n,d1, d2, …, dn,编程需要输出满足d(vi)=di的树的个数。

Input

  第一行是一个正整数n,表示树有n个结点。第二行有n个数,第i个数表示di,即树的第i个结点的度数。其中1<=n<=150,输入数据保证满足条件的树不超过10^17个。

Output

  输出满足条件的树有多少棵。

Sample Input

4 2 1 2 1

Sample Output

2

Hint

  

根据“prufer序列中某个编号出现的次数就是这个编号节点的度数-1”这一性质,这道题就变成了给定元素个数的排列问题。

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<ctime>
#define ll long long using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;} int n;
ll fac[155];
ll ksm(ll t,ll x) {
ll ans=1;
for(;x;x>>=1,t=t*t)
if(x&1) ans*=t;
return ans;
}
bool vis[150];
ll pri[150],cnt[150];
void pre(int n) {
for(int i=2;i<=n;i++) {
if(!vis[i]) pri[++pri[0]]=i;
for(int j=1;j<=pri[0]&&i*pri[j]<=n;j++) {
vis[i*pri[j]]=1;
if(i%pri[j]==0) break;
}
}
}
void work(ll n,int flag) {
for(int i=1;i<=pri[0];i++) {
for(int j=pri[i];j<=n;j*=pri[i]) {
cnt[i]+=flag*n/j;
}
}
}
int tot;
ll ans[5000];
void Cheng(int t) {
for(int i=1;i<=ans[0];i++) ans[i]*=t;
for(int i=1;i<=ans[0];i++) {
ans[i+1]+=ans[i]/10;
ans[i]%=10;
}
while(ans[ans[0]+1]) {
ans[0]++;
ans[ans[0]+1]+=ans[ans[0]]/10;
ans[ans[0]]%=10;
}
} int main() {
n=Get();
pre(n);
work(n-2,1);
int a;
for(int i=1;i<=n;i++) {
a=Get();
if(!a&&n>1) {cout<<0;return 0;}
if(a>1) work(a-1,-1);
tot+=a;
}
if(tot!=(n-1)*2) return cout<<0,0;
if(n==1) {
if(tot) cout<<0;
else cout<<1;
return 0;
}
if(n==2) return cout<<1,0;
ans[0]=1;
ans[1]=1;
for(int i=1;i<=pri[0];i++)
for(int j=1;j<=cnt[i];j++) Cheng(pri[i]);
for(int i=ans[0];i>=1;i--) cout<<ans[i];
return 0;
}

BSOJ 5553 -- 【模拟试题】wangyurzee的树

Description

wangyurzee有n个各不相同的节点,编号从1到n。wangyurzee想在它们之间连n-1条边,从而使它们成为一棵树。 

可是wangyurzee发现方案数太多了,于是他又给出了m个限制条件,其中第i个限制条件限制了编号为u[i]的节点的度数不能为d[i]。 一个节点的度数,就是指和该节点相关联的边的条数。 这样一来,方案数就减少了,问题也就变得容易了,现在请你告诉wangyurzee连边的方案总数为多少。 答案请对1000000007取模。

Input

第一行输入2个整数n(1<=n<=1000000),m(0<=m<=17)分别表示节点个数以及限制个数。 

第2行到第m+1行描述m个限制条件,第i+1行为2个整数u[i],d[i],表示编号为u[i]的节点度数不能为d[i]。 

为了方便起见,保证1<=ui<=m。同时保证1<=ui<=n,1<=di<=n-1,保证不会有两条完全相同的限制。

Output

输出一行一个整数表示答案。

Sample Input

3 1 1 2

Sample Output

2

显然要容斥。我们就用{总的方案数}-{不满足一个条件的方案数}+{不满足两个条件的方案数}-...

注意有个坑点,就是如果我们枚举的两个条件的u相同,那么方案数直接为0。

然后就是如何计数的问题。假设我们已经定了k个点的度数d[1],d[2]...d[k]。设。那么方案数就是

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<ctime>
#define ll long long
#define N 1000005
#define mod 1000000007ll using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;} int n,m;
int u[20],d[20];
vector<int>st[N];
ll fac[N],ans;
ll ksm(ll t,ll x) {
ll ans=1;
for(;x;x>>=1,t=t*t%mod)
if(x&1) ans=ans*t%mod;
return ans;
}
ll C(int n,int m) {return fac[n]*ksm(fac[m],mod-2)%mod*ksm(fac[n-m],mod-2)%mod;}
bool vis[N];
void dfs(int v,int flag,int n,int res,ll tot) {
if(v>m) {
(ans+=flag*tot*ksm(res,n)%mod+mod)%=mod;
return ;
}
dfs(v+1,flag,n,res,tot);
if(n>=d[v]&&!vis[u[v]]) {
vis[u[v]]=1;
dfs(v+1,-flag,n-d[v],res-1,tot*C(n,d[v])%mod);
vis[u[v]]=0;
}
}
int main() {
n=Get(),m=Get();
int a;
for(int i=1;i<=m;i++) {
u[i]=Get();d[i]=Get()-1;
}
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
dfs(1,1,n-2,n,1);
cout<<ans;
return 0;
}

BSOJ 5445 -- 【2018雅礼】树

Description

  有n个点,第i个点的限制为度数不能超过ai。

  现在对于每一个s(1<=s<=n),问从这n个点中选出s个点组成有标号无根树的方案数。

Input

  第一行一个整数表示n。

  第二行n个整数a1~an。

Output

  输出仅一行n个整数,第i个整数表示s=i时的答案。

Sample Input

3 2 2 1

Sample Output

3 3 2

Hint

【数据范围】

  对于20%的数据,n≤6。

  对于60%的数据,n≤50。

  对于100%的数据,n≤100。

我们说过,处理无根树计数的问题可以转化为prufer序列的计数问题。

我们设长度为i的prufer序列,用了j个点的方案数。考虑新增一个点v,我们假设它的度数为,然后就可以得到转移方程

最后特判s=1时,答案是n,s>=2是,答案是

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<ctime>
#define ll long long
#define mod 1004535809ll
#define N 105 using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;} int n;
ll f[N][N],g[N][N],c[N][N],w[N];
int main() {
n=Get();
c[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=i;j++)
c[i][j]=(!j||i==j)?1:(c[i-1][j-1]+c[i-1][j])%mod;
f[0][0]=1;
for(int i=1;i<=n;i++) {
w[i]=Get();
memcpy(g,f,sizeof(f));
for(int j=0;j<n;j++) {
for(int k=0;k<n;k++) {
if(!f[j][k]) continue ;
for(int q=0;q<w[i]&&q+j<=n-2;q++) {
(g[q+j][k+1]+=f[j][k]*c[q+j][q]%mod)%=mod;
}
}
}
memcpy(f,g,sizeof(f));
}
cout<<n<<" ";
for(int i=2;i<=n;i++) cout<<f[i-2][i]<<" ";
return 0;
}

无根树的计数——prufer序列的更多相关文章

  1. 树的计数 + prufer序列与Cayley公式 学习笔记

    首先是 Martrix67 的博文:http://www.matrix67.com/blog/archives/682 然后是morejarphone同学的博文:http://blog.csdn.ne ...

  2. 树的计数 Prufer序列+Cayley公式

    先安利一发.让我秒懂.. 第一次讲这个是在寒假...然而当时秦神太巨了导致我这个蒟蒻自闭+颓废...早就忘了这个东西了... 结果今天老师留的题中有两道这种的:Luogu P4981 P4430 然后 ...

  3. 树的计数 + prufer序列与Cayley公式(转载)

    原文出处:https://www.cnblogs.com/dirge/p/5503289.html 树的计数 + prufer序列与Cayley公式 学习笔记(转载) 首先是 Martrix67 的博 ...

  4. 【XSY1295】calc n个点n条边无向连通图计数 prufer序列

    题目大意 求\(n\)个点\(n\)条边的无向连通图的个数 \(n\leq 5000\) 题解 显然是一个环上有很多外向树. 首先有一个东西:\(n\)个点选\(k\)个点作为树的根的生成森林个数为: ...

  5. Luogu P2290 [HNOI2004]树的计数 Prufer序列+组合数

    最近碰了$prufer$ 序列和组合数..于是老师留了一道题:P2624 [HNOI2008]明明的烦恼 qwq要用高精... 于是我们有了弱化版:P2290 [HNOI2004]树的计数(考一样的可 ...

  6. BZOJ 1211 HNOI2004 树的计数 Prufer序列

    题目大意:给定一棵树中全部点的度数,求有多少种可能的树 Prufer序列.详细參考[HNOI2008]明明的烦恼 直接乘会爆long long,所以先把每一个数分解质因数.把质因数的次数相加相减.然后 ...

  7. 【BZOJ1211】【HNOI2004】树的计数 prufer序列

    题目描述 给你\(n\)和\(n\)个点的度数,问你有多少个满足度数要求的生成树. 无解输出\(0\).保证答案不超过\({10}^{17}\). \(n\leq 150\) 题解 考虑prufer序 ...

  8. 【BZOJ1005/1211】[HNOI2008]明明的烦恼/[HNOI2004]树的计数 Prufer序列+高精度

    [BZOJ1005][HNOI2008]明明的烦恼 Description 自从明明学了树的结构,就对奇怪的树产生了兴趣......给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可 ...

  9. bzoj1211: [HNOI2004]树的计数 prufer序列裸题

    一个有n个结点的树,设它的结点分别为v1, v2, …, vn,已知第i个结点vi的度数为di,问满足这样的条件的不同的树有多少棵.给定n,d1, d2, …, dn,编程需要输出满足d(vi)=di ...

随机推荐

  1. vscode使用汇总——常用插件、常用配置、常用快捷键

    一.代码提示快捷键设置:(keybindings.json) [ { "key": "ctrl+j", "command": "- ...

  2. KM算法及其应用

    在二分图匹配中有最大匹配问题,使用匈牙利算法或者网络流相关算法解决,如果给每条边增加一个权值,求权值和最大的匹配方案就叫做最大权匹配问题.其实之前所说的最大匹配就是权值为1的最大权匹配. 求最大权完备 ...

  3. RPA流程自动化-Blueprism认证考试介绍

    RPA流程自动化-Blueprism认证考试介绍 接触RPA有一段时间了,几种RPA相关工具也都试用过,BluePrism是RPA工具的一种,今天跟大家分享考Blueprism的一些经验. RPA(R ...

  4. Spark练习代码

    1.scalaWordCount package com._51doit.spark.day1 import org.apache.spark.rdd.RDDimport org.apache.spa ...

  5. 安装Java语言的jdk,配置java环境变量

    一.windows 安装jdk win7 下载jdk: 地址   https://www.oracle.com/technetwork/java/javase/downloads/index.html ...

  6. 设计模式之访问者模式(Visitor )

    访问者模式是一种将数据操作和数据结构分离的设计模式,可以说是面向数据密集型的一种设计方式,数据的结构相对稳定,有明显的分层和分类,而对数据对象的相关操作进行分组.分析等二次加工,这些操作都是由访问者来 ...

  7. SVN多项目并行版本管理解决方案

    1.背景 随着公司业务拓展,各业务部门频繁的需求变更,导致系统集成冲突的问题日益突出. 2.现状 基于SVN版本管理模式,多分支版本并行,分支合并主干交付.多分支开发存在依赖关系且有交付的先后顺序, ...

  8. maven+eclipse创建web项目

    第一步,创建maven工程,如下图步骤 选择maven-archetype-webapp,然后next 输入GroupId和ArtifactId,Package可以为空,然后finish 新创建的ma ...

  9. 从函数式编程到Promise

    译者按: 近年来,函数式语言的特性都被其它语言学过去了.JavaScript异步编程中大显神通的Promise,其实源自于函数式编程的Monad! 原文: Functional Computation ...

  10. css 单位px、em、rem、vh、vw、vmin、vmax区别

    1.px:相对长度单位.像素px是相对于显示器屏幕分辨率而言的. 2.em:相对长度单位.相对于当前对象内文本的字体尺寸.如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸. 看下面 ...