【点分治总结】

点分治教程

例题
给定一棵带权树,显然共有N*(N-1)/2条边,问:第k小的边边长多长?
N<=10000.

题解:
这道题直接上手做实在是太难了,需要逐步拆分。
首先,问题是第k小的边的边长,这个问题不好解,但是转换一下问题,二分第k小的边长T,然后判断这棵树中<=T的路径有多少条,这个能稍微好做一下,至少变成了一个统计性问题。
二分后题目变成:
给定一棵带权树,显然共有N*(N-1)/2条边,问:<=T的边有多少条(POJ1741)。
显然的,对于整个树的根来说,路径只有两种:
1.经过根的路径
2.不经过根的路径
显然,不经过根的所有路径,都单独属于某个根的子树中。

因此,我们完全可以这么做此题:
void solve(int i)//表示统计以i为根的这棵树中的满足题目要求的路径个数。
{
work(i); //统计所有经过根节点i的满足要求的路径个数。
delete(i); //把节点i从整棵树中删掉,这样i的所有儿子就形成了不同的子树
for (i的任何一个儿子j)
{
if (j属于i的某个子树中)
solve(j); //统计以j为根节点的子树中满足要求的路径个数
}
}

例如:共有一颗树,联通情况如下:
1
2————|————3
4——|——5 6——|——7
那么,首先调用solve(1),处理所有经过1的路径,然后把1删掉,调用solve(2),solve(3)分别处理,solve(2)处理完后调用了solve(4),solve(5),solve(3)以此类推。
这么做显然是明智的,把一个规模为n的问题,通过计算一个比较简单的子问题,转换成了两个规模为2n的问题。
如果计算这个比较简单的子问题的复杂度是nlogn的话,那么总的复杂度就是nlognlogn。
然而,这么做有一个反例:
1——2——3——4——5——6——7
首先调用solve(1),然后调用solve(2),然后调用solve(3)…
如此一来复杂度退化成了n*nlogn

为了避免这一种退化,我们可以人为的规定每棵子树的根,对于上述问题:
首先调用solve(4),然后两颗子树就是1——2——3,5——6——7,再分别调用solve(2),solve(6)。
这样,最多经过logn层,势必把所有的节点都处理完毕了。

而每棵子树的根很好选择,显然就是当前子树的重心!用两遍dfs可以求得。

solve的代码非常好写,如下:
void solve(int now)  //表示solve以now为根的子树,此时now已经是重心
{  
    int u;  
    ans += work(now);  //work(now)返回的是经过now的路径中,满足题意的数量。
    done[now] = 
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值