[关闭]
@rebirth1120 2019-07-04T09:56:44.000000Z 字数 1149 阅读 810

点分治

OI知识

参考资料:洛谷日报2018,8 #23 [守望] 点分治略解

一、主要用途

处理树上有关路径的问题,如:求一棵树上长度为 k 的路径有多少条。

二、思想

分治 (毫无疑问)

分析:

对于树上的每一个点,一条路径有两种情况
1. 经过这个点
2. 在这个点的子树上

那么对于情况 1,我们可以 dfs 算出它所有子树上的节点到它的距离,从而求出长度为 k 的路径数量;
而对于情况 2,利用分治的思想,我们可以对当前节点的每一棵子树进行对情况 1 的处理,这样递归下去,最终就可以求得答案。

三、具体实现

大体分为三个部分
1.找当前树的重心 u (为了使递归次数尽量的少)
2.求出 u 的子树中的所有节点到 u 的距离
3.统计, 计算出符合条件的路径数量
4.分治, 对 u 的每棵子树进行操作 1

四、代码

找重心

  1. void get_rt(int u,int f){
  2. int maxn=0;size[u]=1; // 这里 size 必须先初始化为 1
  3. for(int i=lst[u];i;i=nxt[i]){
  4. int v=to[i];
  5. if(v==fa||vis[v]) continue; // 由于要多次找重心,所以要避开已经扫过的点
  6. get_rt(v,u);
  7. size[u]+=size[v];
  8. maxn=max(maxn,size[v]);
  9. }
  10. maxn=max(maxn,num-size[u]);
  11. if(maxn<maxp){
  12. maxp=maxn;
  13. rt=u;
  14. }

求距离

  1. void get_dis(int u,int fa,int dis){
  2. if(dis>k) return;
  3. bot[++bot[0]]=dis; // 先存起来,以便等下统计
  4. for(int i=lst[u];i;i=nxt[i]){
  5. int v=to[i];
  6. if(v==fa||vis[v]) continue;
  7. get_dis(v,u,dis+len[i]);
  8. }

统计

  1. void calc(int u){
  2. t=0;
  3. tms[0]=1; // 到节点 u 不同长度的点出现的次数
  4. for(int i=lst[u];i;i=nxt[i]){
  5. int v=to[i];
  6. if(vis[v])continue;
  7. bot[0]=0; // 初始化,清零
  8. get_dis(v,u,len[i],1);
  9. for(int j=1;j<=bot[0];j++)
  10. ans+=tms[k-bot[j]]; // 统计
  11. for(int j=1;j<=bot[0];j++){
  12. q[++t]=b1[j];
  13. tms[bot[j]]++; // 统计完一棵子树后再把它存进去,防止统计到同一子树中的节点
  14. }
  15. }
  16. for(int i=1;i<=t;i++)
  17. tms[q[i]]=0; // 由于 k 可能比较大,用 memset 有可能 TLE,所以用一个队列存修改了的点,最后再一一清零
  18. }

五、练习

模版1
模板2(求距离为k的点对个数)
求小于k的点对个数
权值和为k,求最小边数

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注