最近学习了Prüfer编码与Cayley公式,这两个强力的工具一般用于解决树的计数问题。现在博主只能学到浅层的内容,只会用不会证明。

推荐博客:https://blog.csdn.net/morejarphone/article/details/50677172 (Prüfer编码与树的转换)

https://www.cnblogs.com/dirge/p/5503289.html (几类树的计数问题)

主要的知识还是挺少的,

树转成Prufer编码:找到当前叶子节点中编号最小的那个点x,输出与x相邻的点,删掉x,这样重复n-2次(最后剩下两个点停止)。

Prufer编码转树:由于没有出现在序列里的序号恰好是是叶子节点,所以每次找到每次找到序号最小的叶子节点,与序列的对应项形成一条边,把这个叶子删掉,继续考虑即可。

Prufer编码与树是一一对应的,所以很容易用Prufer编码得到Cayley公式:n个点的完全图的生成树个数个数是n^n−2。

学了一下我感觉树的计数问题还是有许多类的,按照 有/无标号+有/无根 就可以分成4类,而Cayley公式就是直接解决了有标号无根树计数。

题目练习:

BZOJ 1430 小猴打架

打架的双方以及它们的好朋友就会互相认识这句话意思就是最后没架打的时候关系图会形成一棵有标号的无根树。直接应用Cayley公式那么关系图数量就是n^(n-2)。然后题目还要求打架的顺序,就是形成关系图的连边顺序(n-1)条边就是(n-1)!。答案就是 n^(n-2)*(n-1)! 。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int P=9999991;
int n;int main()
{cin>>n;long long ans=1;for (int i=1;i<=n-2;i++) ans=(ans*n)%P;for (int i=1;i<=n-1;i++) ans=(ans*i)%P;cout<<ans<<endl;    return 0;
} 

View Code

BZOJ 1211 树的计数

给出n个点及其n个点的度数,问满足条件的树的个数。先要知道一个性质:prufer序列中某个编号出现的次数就等于这个编号的节点在无根树中的度数-1

因为prufer编码和树的形态是一一对应的,那么我们就可以从prufer编码的排列来思考这个问题。根据上面的性质,编号i的点度数为di,那么i这个数字就会再prufer编码中出现di-1次,对于每个i都是如此且它们的总数为n-2。那么问题变成这n-2个带重复数字的排列问题(即多重集的排列)。那么答案就出来了 ans=(n-2)!/[(d1-1)!*(d2-1)!*...(dn-1)!]。

因为这题中间乘法结果会爆long long,这里我学的是hzwer学长因数分解的办法。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long LL;
int n,d[N],sum;
map<LL,int> c;void divide(long long n,int b) {for (int i=2;i<=sqrt(n);i++) {if (n%i==0) {c[i]=0;while (n%i==0) n/=i,c[i]+=b;}}if (n>1) c[n]+=b;
}int main()
{cin>>n;for (int i=1;i<=n;i++) scanf("%d",&d[i]),sum+=d[i];if (n==1) return printf("%d\n",d[1]?0:1),0;  //n=1的时候不用Cayley公式,特判 if (sum!=2*(n-1)) return puts("0"),0;  //不是一棵树 for (int i=1;i<=n;i++) if (d[i]<=0) return puts("0"),0;for (int i=1;i<=n-2;i++) divide(i,1);for (int i=1;i<=n;i++) divide(d[i]-1,-1);long long ans=1;for (map<LL,int>::iterator i=c.begin();i!=c.end();i++) for (int j=1;j<=i->second;j++)ans*=i->first;cout<<ans<<endl;        return 0;
}

View Code

BZOJ1005 明明的烦恼

这题是上一题的加强版,有的点度数是不做要求的。

还是顺着上题思路,完整的prufer序列长度应该为n-2,但是因为有的点度数缺失所以s=sigma(di-1)<=n-2,剩下的res=(n-2)-s。我们先把确定的填下prufer序列,方案数是C(n-2,s)*s!/[(d1-1)!*(d2-1)!...(dn-1)!]。接下来我们就要考虑prufer序列剩下的res个位置填度数缺失的点,令度数确定的点个数为cnt,那么剩下的res个位置每个位置可以随便填n-cnt个数,方案数是(n-cnt)^res。两个方案数相乘即是答案。

因为数字巨大,所以套个大数板子就可以AC。

但是我有一点没想通的是:剩下的res个位置为什么可以随便填?这样不会出现有的点度数为0的情况吗?这样不是有可能产生多棵树?我只能理解为多棵树也是可以接受的。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long LL;
int n,d[N],cnt,sum;typedef long long LL;#define MAXN 9999
#define MAXSIZE 10
#define DLEN 4class BigNum
{
private: int a[3000];    //可以控制大数的位数 int len;       //大数长度
public: BigNum(){ len = 1;memset(a,0,sizeof(a)); }   //构造函数BigNum(const int);       //将一个int类型的变量转化为大数BigNum(const char*);     //将一个字符串类型的变量转化为大数BigNum(const BigNum &);  //拷贝构造函数BigNum &operator=(const BigNum &);   //重载赋值运算符,大数之间进行赋值运算
friend istream& operator>>(istream&,  BigNum&);   //重载输入运算符friend ostream& operator<<(ostream&,  BigNum&);   //重载输出运算符
BigNum operator+(const BigNum &) const;   //重载加法运算符,两个大数之间的相加运算 BigNum operator-(const BigNum &) const;   //重载减法运算符,两个大数之间的相减运算 BigNum operator*(const BigNum &) const;   //重载乘法运算符,两个大数之间的相乘运算 BigNum operator/(const int   &) const;    //重载除法运算符,大数对一个整数进行相除运算
BigNum operator^(const int  &) const;    //大数的n次方运算int    operator%(const int  &) const;    //大数对一个int类型的变量进行取模运算    bool   operator>(const BigNum & T)const;   //大数和另一个大数的大小比较bool   operator>(const int & t)const;      //大数和一个int类型的变量的大小比较void print();       //输出大数
};
BigNum::BigNum(const int b)     //将一个int类型的变量转化为大数
{ int c,d = b;len = 0;memset(a,0,sizeof(a));while(d > MAXN){c = d - (d / (MAXN + 1)) * (MAXN + 1); d = d / (MAXN + 1);a[len++] = c;}a[len++] = d;
}
BigNum::BigNum(const char*s)     //将一个字符串类型的变量转化为大数
{int t,k,index,l,i;memset(a,0,sizeof(a));l=strlen(s);   len=l/DLEN;if(l%DLEN)len++;index=0;for(i=l-1;i>=0;i-=DLEN){t=0;k=i-DLEN+1;if(k<0)k=0;for(int j=k;j<=i;j++)t=t*10+s[j]-'0';a[index++]=t;}
}
BigNum::BigNum(const BigNum & T) : len(T.len)  //拷贝构造函数
{ int i; memset(a,0,sizeof(a)); for(i = 0 ; i < len ; i++)a[i] = T.a[i];
}
BigNum & BigNum::operator=(const BigNum & n)   //重载赋值运算符,大数之间进行赋值运算
{int i;len = n.len;memset(a,0,sizeof(a)); for(i = 0 ; i < len ; i++) a[i] = n.a[i]; return *this;
}
istream& operator>>(istream & in,  BigNum & b)   //重载输入运算符
{char ch[MAXSIZE*4];int i = -1;in>>ch;int l=strlen(ch);int count=0,sum=0;for(i=l-1;i>=0;){sum = 0;int t=1;for(int j=0;j<4&&i>=0;j++,i--,t*=10){sum+=(ch[i]-'0')*t;}b.a[count]=sum;count++;}b.len =count++;return in;}
ostream& operator<<(ostream& out,  BigNum& b)   //重载输出运算符
{int i;  cout << b.a[b.len - 1]; for(i = b.len - 2 ; i >= 0 ; i--){ cout.width(DLEN); cout.fill('0'); cout << b.a[i]; } return out;
}BigNum BigNum::operator+(const BigNum & T) const   //两个大数之间的相加运算
{BigNum t(*this);int i,big;      //位数   big = T.len > len ? T.len : len; for(i = 0 ; i < big ; i++) { t.a[i] +=T.a[i]; if(t.a[i] > MAXN) { t.a[i + 1]++; t.a[i] -=MAXN+1; } } if(t.a[big] != 0)t.len = big + 1; elset.len = big;   return t;
}
BigNum BigNum::operator-(const BigNum & T) const   //两个大数之间的相减运算
{  int i,j,big;bool flag;BigNum t1,t2;if(*this>T){t1=*this;t2=T;flag=0;}else{t1=T;t2=*this;flag=1;}big=t1.len;for(i = 0 ; i < big ; i++){if(t1.a[i] < t2.a[i]){ j = i + 1; while(t1.a[j] == 0)j++; t1.a[j--]--; while(j > i)t1.a[j--] += MAXN;t1.a[i] += MAXN + 1 - t2.a[i]; } elset1.a[i] -= t2.a[i];}t1.len = big;while(t1.a[len - 1] == 0 && t1.len > 1){t1.len--; big--;}if(flag)t1.a[big-1]=0-t1.a[big-1];return t1;
} BigNum BigNum::operator*(const BigNum & T) const   //两个大数之间的相乘运算
{ BigNum ret; int i,j,up; int temp,temp1;   for(i = 0 ; i < len ; i++){ up = 0; for(j = 0 ; j < T.len ; j++){ temp = a[i] * T.a[j] + ret.a[i + j] + up; if(temp > MAXN){ temp1 = temp - temp / (MAXN + 1) * (MAXN + 1); up = temp / (MAXN + 1); ret.a[i + j] = temp1; } else{ up = 0; ret.a[i + j] = temp; } } if(up != 0) ret.a[i + j] = up; } ret.len = i + j; while(ret.a[ret.len - 1] == 0 && ret.len > 1)ret.len--; return ret;
}
BigNum BigNum::operator/(const int & b) const   //大数对一个整数进行相除运算
{ BigNum ret; int i,down = 0;   for(i = len - 1 ; i >= 0 ; i--){ ret.a[i] = (a[i] + down * (MAXN + 1)) / b; down = a[i] + down * (MAXN + 1) - ret.a[i] * b; } ret.len = len; while(ret.a[ret.len - 1] == 0 && ret.len > 1)ret.len--; return ret;
}
int BigNum::operator %(const int & b) const    //大数对一个int类型的变量进行取模运算
{int i,d=0;for (i = len-1; i>=0; i--){d = ((d * (MAXN+1))% b + a[i])% b;  }return d;
}
BigNum BigNum::operator^(const int & n) const    //大数的n次方运算
{BigNum t,ret(1);int i;if(n<0)exit(-1);if(n==0)return 1;if(n==1)return *this;int m=n;while(m>1){t=*this;for( i=1;i<<1<=m;i<<=1){t=t*t;}m-=i;ret=ret*t;if(m==1)ret=ret*(*this);}return ret;
}
bool BigNum::operator>(const BigNum & T) const   //大数和另一个大数的大小比较
{ int ln; if(len > T.len)return true; else if(len == T.len){ ln = len - 1; while(a[ln] == T.a[ln] && ln >= 0)ln--; if(ln >= 0 && a[ln] > T.a[ln])return true; elsereturn false; } elsereturn false;
}
bool BigNum::operator >(const int & t) const    //大数和一个int类型的变量的大小比较
{BigNum b(t);return *this>b;
}void BigNum::print()    //输出大数
{ int i;   cout << a[len - 1]; for(i = len - 2 ; i >= 0 ; i--){ cout.width(DLEN); cout.fill('0'); cout << a[i]; } cout << endl;
}int main()
{cin>>n;for (int i=1;i<=n;i++) scanf("%d",&d[i]);if (n==1) return printf("%d\n",d[1]>0?0:1),0;  //n=1的时候不用Cayley公式,特判 for (int i=1;i<=n;i++) if (d[i]>0) cnt++,sum+=d[i]-1;BigNum ans=1;for (int i=1;i<=n-2;i++) ans=ans*i;for (int i=1;i<=n-2-sum;i++) ans=ans/i;//for (int i=1;i<=sum;i++) divide(i,-1);//for (int i=1;i<=sum;i++) divide(i,1);for (int i=1;i<=n;i++) if (d[i]>0)for (int j=1;j<=d[i]-1;j++) ans=ans/j;for (int i=1;i<=(n-2)-sum;i++) ans=ans*(n-cnt);        cout<<ans<<endl;        return 0;
}

View Code

转载于:https://www.cnblogs.com/clno1/p/11422156.html

树的计数 Prüfer编码与Cayley公式 学习笔记相关推荐

  1. 《编码checklist规范》学习笔记

    <编码checklist规范>学习笔记 <编码checklist规范>posts <编码checklist规范>学习笔记 0 前言 1 排版 1.0 总则 1.1 ...

  2. 电机学变压器涉及公式学习笔记(待补全)

    文章目录 变压器涉及公式学习笔记 绪论 铁磁材料及其特性 磁路相关的基本物理定律 变压器的基本工作原理和结构 额定值 变压器的运行分析 空载运行 负载运行 参数测定 变压器运行性能 变压器涉及公式学习 ...

  3. 【学习笔记】树的计数,prufer(Prüfer)编码,Cayley公式及相应例题

    目录 1.pruferpruferprufer编码 1)无根树转化为prufer序列 2)prufer序列转化为无根树. 2.Cayley公式 1)由Cayley公式得到四个推论 例题1.P4981 ...

  4. b+树时间复杂度_前端大神用的学习笔记:线段树和树状数组

    全文篇幅较长,细心理解一定会有收获的♪(^∇^*). 1|0线段树 1|1一些概念     线段树是一种二叉搜索树,每一个结点都是一个区间(也可以叫作线段,可以有单点的叶子结点),有一张比较形象的图如 ...

  5. gpb编码 c语言,ARM学习笔记--GPIO接口

    GPIO(General Purpose I/O Ports)意思为通用输入/输出端口,通俗地说,就是一些引脚,可以通过它们输出高低电平或者通过它们读入引脚的状态-是高电平或是低电平. S3C2410 ...

  6. Excel 公式学习笔记:获取当前文件名

    CELL函数:获得指定单元格地址.值.文件路径等信息. CELL(要获取的信息类型, 获取谁?默认最后一个编辑过的单元格) FIND函数 :在字符串中查找子串的位置.首字母编号 1 FIND(要查找的 ...

  7. van Emde Boas Trees(vEB树)(Introduction to Algorithms, 算法导论,CLRS)学习笔记

    van Emde Boas Trees 1. Predecessor search/ordered sets predecessor: return the nearest left neighbor ...

  8. 解题报告(八) prufer 序列与 Cayley 公式(ACM / OI)超高质量题解

    繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我写的超高质量题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数(1∼51 \sim 51∼5),以模板题难度 11 ...

  9. bzoj 1211 [HNOI2004]树的计数

    [HNOI2004]树的计数 Description 一个有n个结点的树,设它的结点分别为v1, v2, -, vn,已知第i个结点vi的度数为di,问满足这样的条件的不同的树有多少棵.给定n,d1, ...

最新文章

  1. 斯坦福马腾宇:用显式正则器提升深度神经网络的泛化能力
  2. 数据结构(严蔚敏)之二——链表的c语言实现
  3. 所见即所得的开源跨平台 Markdown 编辑器
  4. STM32学习笔记之一(初窥STM32)
  5. DirectUpdateHandler2 Solr commit
  6. 怎么让手机变成震动器_手机厂商都在说的线性马达,到底是个什么东西?
  7. VC6.0建立控制台程序实现PDA应用
  8. 命名集 —— 名字结构
  9. “NLP的那些事儿”开张了!
  10. 测试压缩ASP.NET中的ViewState
  11. ⭐️ vue项目使用微信表情;vue引入微信表情emoji;vue中使用微信表情包emoji;
  12. 国内首家!携程周三、周五可在家“躺平”:76%员工主动报名 !网友:我酸了...
  13. 【JSP篇】——6.JSP之小学生在线答题系统【综合实战篇】
  14. centos7dos命令下打开网络
  15. IS_REACHABLE
  16. 常见的系统架构风格有哪些?各有什么优缺点?
  17. 【干货】 xgboost如何自定义eval_metric ( feval ) ?
  18. 爬虫案例 王者荣耀 皮肤壁纸下载
  19. oracle 主要特点是,Oracle PL/sql 主要特点
  20. 生成模型——自回归模型详解与PixelCNN构建

热门文章

  1. 金蝶EAS登陆客服端时报错
  2. 数模优秀论文总结 — 2017 “拍照赚钱的任务定价”
  3. 微信小程序开发环境/正式环境介绍。
  4. 小学生python趣味编程-图书推荐:《Scratch 3.0少儿游戏趣味编程》
  5. DAY 11——12 零零碎碎
  6. 【Python知识】 可哈希和不可哈希对象
  7. 人工智能概率第一次作业
  8. 说服他人的5种技巧 – Guy Kawasaki
  9. Nexus9000 版本ACI和nxos切换
  10. HTML九九乘法表的灵活应用