树的统计
来源:互联网 发布:讲主角进入网络电影 编辑:程序博客网 时间:2024/06/05 01:11
原题:一颗含有n个结点的树,所有的结点依次编号为1,2,3,……,n.对于编号为v的结点,定义t(v)为v的后代中所有编号小于v的结点个数。输入这棵树,请输出t(1),t(2),t(3),……,t(n)。
当然,对于本题,写出一个O(n2)的算法是一件很容易的事,这显然也不是接下来要推出的理想算法。
先深度优先遍历该树,按照访问的先后顺序排列结点:7 10 14 12 13 1 9 11 6 5 8 3 15 12 4(DFS序列)。然后把每个结点的子结点先后顺序反转,重新遍历,可得到新的排序结点序列:7 4 3 12 15 9 6 8 5 11 1 10 14 13 2(逆DFS序列)。以结点9为代表,显然,把两次遍历过程中在结点9之后被访问的点圈出来,得到下图。
不难发现,两个线框的重叠区域,即为结点9的所有后代结点。看到上面的图,估计有组合数学知识的人都能想到容斥原理。上图中除了线框1和2包括的部分,还包括了结点9本身和它的直系祖先结点。
定义f(v,S)表示在S所描述的集合中,结点编号小于v的个数。则对于9号结点,有:
f(9,线框1)+f(9,线框2)+f(9,9的直系祖先)-f(9,整棵树)=f(9,9的后代)
推广到更一般的情况,有
f(v,DFS序列中v之后的部分)+f(v,逆DFS序列中v之后的部分)+f(v,v的直系祖先)-f(v,整棵树)=f(v,v的后代)
显然,我们有f(v,整棵树)=v-1,而f(v,v的后代)就是我们的目标函数t(v),从而有:
t(v)=f(v,DFS序列中v之后的部分)+f(v,逆DFS序列中v之后的部分)+f(v,v的直系祖先)-(v-1)
求区间内小于或大于某个数的个数,我们可以通过线段树来解决,从而,可以在一次DFS遍历中(O(n))加上动态的线段树的操作(O(logn)),就能以O(nlogn)的时间复杂度和O(n)的空间复杂度求出所有结点的直系祖先中有多少个比它本身小;对DFS序列和逆DFS序列,可以用线段树求出每个元素后面有多少个比它本身小,时间复杂度也为O(nlogn),从而整个算法的时间复杂度为O(nlogn)。
照着算法随手写了个,c++代码实现如下:
输入:
输出:
总结:
这个算法最巧妙的地方是把树对应到了两个序列:DFS序列和逆DFS序列。我们知道,树的数据关系要比线性序列复杂。本来在树中,某个结点和它的后代之间存在具有层次性的拓扑关系;通过变换成序列后,这个关系简化成了一个线性序列中的先后关系。而正是这个简单的先后关系,使我们能够用线段树(或者树状数组)来解决问题。
来源:
吴文虎、王建德 《世界大学生程序设计竞赛(ACM/ICPC)高级教程 第一册 程序设计中常用的计算思维方式》
- 树的统计
- 【ZJOI2008】树的统计
- [bzoj1036]树的统计
- 【ZJOI2008】树的统计
- 【ZJOI2008】树的统计
- 【ZJOI2008】树的统计
- 树的统计
- 树的种类统计
- 树的统计count
- 【Bzoj1036】树的统计
- 树的统计【bzoj1036】
- 树的统计 ZJOI2008
- [ZJOI2008]树的统计
- [ZJOI2008]树的统计Count
- [ZJOI2008]树的统计Count
- hybz1036 树的统计Count
- Bzoj 1036 树的统计
- BZOJ1036 树的统计Count
- InvocationHandler的一个例子
- jquery之利用ajax与服务器交谈(温习ajax之发起请求)
- Asp.net输出内容不缓存代码
- 用WTL实现自定义绘制控件
- c++进阶之---istream/ostream简介
- 树的统计
- Response.End()出错可以替换为HttpContext.Current.ApplicationInstance.CompleteRequest()
- C/C++consloe 中动态显示进度
- 全力打造互联网推广最优秀的超级工具
- php中的引用传值分析
- mysql 死锁处理
- c++进阶之---ifstream/ofstream
- 项目实践与用OpenGL实现OBJ模型文件的读入
- lucene索引结构的整理