本文转自http://fanhq666.blog.163.com/blog/static/81943426201172472943638/
求树重心的方法:(NlogN)
http://www.cnblogs.com/qlky/p/5780933.html
还记得曾经提到过的树的“重心”吗?重心的定义是:以这个点为根,那么所有的子树(不算整个树自身)的大小都不超过整个树大小的一半。
树的重心的一个的性质:
树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么他们的距离和一样。
这也是“道路修建”带来的启发。(证明:调整法)
树的重心的另一个性质:
把两个树通过一条边相连得到一个新的树,那么新的树的重心在连接原来两个树的重心的路径上。
这个让“重心”名副其实了。。。(证明:。。。自己好好思考一下吧。。。)
还有一个性质:
把一个树添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
(证明:提示:张放想都没想说,要不然那两个不等式就矛盾了)
嗯,不错,有这么多性质,可以出不少恶心题了。。。
不过,我还是更关心一个事情:重心的动态维护。
如何动态呢?
情景1:添加一片叶子
根据已有性质,添加一片叶子之后新的重心要么不动要么向那片叶子的方向移动一下。这样,可以用一个link-cut tree来维护。
我们以重心为根建立这个动态树。每个节点维护它所在的子树的大小。添加叶子等于向一条路径上的维护值增加1,这个可以通过打标记实现。发现不得不移动的时候进行一次换根的操作。因为只可能移动1,所以换根的操作是可以完成的。
我们甚至还可以维护所有点到重心的距离和!这个只需给每个点加一个维护值:这个点为根的子树中所有点到这个点的距离和,通过稍微有点复杂的标记系统还是可以维护的。
情景2:删除一片叶子
只有删除操作?那么离线改成添加吧。。。
不允许离线?那么我们要换一个思路:
定义稍微广义的树的重心:每个点有一个非负权,每个边有个正的长度,到所有点的权乘以距离的和最小的点定义为重心。
注意:树的重心的位置和边的长度没有关系!。
在只有权值修改的情况下,我们可以利用树分治配合基本数据结构来维护树的重心:
注意到,我们可以维护一个子树内的点的权值和(利用dfs序)。这样给定一条边,我们就能够知道树的重心在这条边的哪边(看看哪边权值和大就行了)。
这样,我们可以先找一个比较靠近中心的边,问问应该向哪边走,再分治下去,就像树分治那样(类似二分查找?)。
当然,要想一下其他的技巧来对付”星型数据“,这个应该不难(通过拆点、拆边的技巧)。
利用这个广义一点的重心,我们发现,删除操作其实就是把权修改成0而已,可以在log^2N的时间内动态维护了。
如何处理多重心的情况?”抖动“一下权值使得只有一个重心不就行了。。。那另一个重心在哪里?这个只是个细节问题。。。
能否维护距离和?能否在logN的时间内维护?欢迎讨论(将子树和查询与树分治结合起来?。。。)。
情景3:移动一片叶子
把一个叶子移动到另一个地方。
这个怎么维护呢?其实,我们发现,新的重心应该在原来的重心和叶子新的位置的连线上(证明?应该是对的吧),移动距离很小。于是,也就可以维护了。
情景4:移动一个子树(被移动的子树小于原树的一半,并且保持它的根不变)
这个可以维护吗?
新的重心在原来重心和新子树的接合点的连线上吗?(证出来的欢迎留言)
有一个可以和link-cut tree配合使用的工具,叫做Euler-tour tree,它维护树的欧拉回路,基本元素是边。利用它,可以方便的完成子树的移动,并且给定一条有向边,可以回答这条边指向的子树的大小(Eurler-tour tree中没有”根“以及”父亲“这个概念!)。如果上面的那个论断是对的,那么这个应该可以维护了(复杂度?log^2N吧。。。)
另一个思路是树块划分,把树划分成若干联通块,并且在查询的时候合并相邻的联通块使得每个联通块的大小都是sqrt(N)的级别。这个东西对维护是否有帮助?欢迎交流。。。
情景5:开始N个一个点的树,每次用一条边合并两个树,要求回答新的树的重心
离线?在线?logN?log^2N?sqrt(N)?
等待你去探索
情景6:未来的某一天,某省省选出了一个叫做”疯狂的重心“的数据结构题,时限1分钟,内存若干G,标程几十K,当场无人做。。。。。。