当前位置: 首页 > news >正文

poj-1741 (点分治模板)

题目

Description

Give a tree with n vertices,each edge has a length(positive integer less than 1001).
Define dist(u,v)=The min distance between node u and v.
Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k.
Write a program that will count how many pairs which are valid for a given tree.

Input

The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l.
The last test case is followed by two zeros.

Output

For each test case output the answer on a single line.

Sample Input

5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0

Sample Output

8

分析

  • 题目大意就是给一棵树,每条边有一个长度,两个点之间的距离就是连接它俩的边的长度之和。求出这棵树中满足距离不大于 K 的点对个数。
  • 这基本上就是树的点分治模板题,我也是第一次打树的分治,主要有以下几个步骤:
  • 对于一棵子树的分治,首先求出它的重心, 我们目的是对这棵子树讨论所有经过重心的路径。(重心的作用是保证时间复杂度)
  • 之后,以此重心为根节点,对这棵树进行一遍深搜,得出每个节点到重心的距离dis[]
  • 之后,这棵子树中相连的路径会经过重心且对答案有贡献的点对(i,j) (i<j)就会是这样: dis[i]+dis[j] <= K 且在去除重心后,i 与 j 不在同一个联通块里
  • 不过显然要满足“不在同一个联通块里”这个条件有点突兀,于是就有了一个小技巧:
  • 先不管在不在一个联通块这个条件,算出当前这棵树的符合路径数,之后再将得出的个数减去 以重心的儿子节点为根的子树内 的 点对 路径距离(经过重心)小于等于K的个数,就行了。
  • (可能这句话有点绕,不会的话看看程序再理解理解,应该也是能领悟到的)
  • 之后,再分治一下现在这棵子树,步骤同上。

  • 一棵子树的重心其实就是你要找到一个点,使得删掉这个点后,这棵子树剩下最大(节点个数最多)联通块最小。
  • 为什么每次都要算一个重心呢?你想想,要是那一棵子树刚好是一条链(就是一整条下来),而默认的根节点又在链的端点上,那么时间不就退化到了还不如暴力的程度了?于是我们求个重心,把时间复杂度保证在了 log 级别里面,就很多了。

程序

#include <cstdio>
#include <algorithm>
#define Max 20010
#define add(u,v,w) (To[++num]=Head[u],Head[u]=num,V[num]=v,W[num]=w)
#define For(x) for (int h=Head[x],o=V[h]; h; h=To[h],o=V[h])
#define Input for (int i=1,u,v,w; i<N; i++) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w)
using namespace std;
int N,K,root,num,ans,cnt,mins,Head[Max],To[Max],V[Max],W[Max],siz[Max],dis[Max],f[Max];
/*
    程序中部分变量说明
    dis[i]  所有路径到 重心 的长度
    siz[i]  以 i 为根节点的子树的大小(当前重心下)
    f[i]    i 是否还存在(分治完一次后就把重心删掉了)
    cnt     记录 dis 的个数(即路径个数)
    root    当前子树的重心
    maxs    当前讨论的点所有子树大小中最大值(并不是全局变量,是尝试每个重心时重新开的一个变量)
    mins    讨论过的点的子树大小中最大的最小值(是全局变量,用来确定哪个才是重心)
*/

int get_size(int x,int fa){     //返回以 x 为根的子树大小,其中 x 父节点为 fa 
    siz[x]=1;
    For(x) if (o!=fa && !f[o])
        siz[x]+=get_size(o,x);
    return siz[x];
}

void get_dis(int x,int d,int fa){   //x 到重心的长度为 d,之后继续 dfs 
    dis[++cnt]=d;
    For(x) if (o!=fa && !f[o])
        get_dis(o,d+W[h],x);
    return;
}

void dfs_root(int x,int tot,int fa) {
    //求目标子树的重心(要求除去 x 点时,它的 maxs 值最小,那么 x 就是这棵子树的重心了),其中 tot 是这棵子树的总大小(节点个数) 
    int maxs=tot-siz[x];    //这棵子树中x 父亲那一支先赋给 maxs 
    For(x) if (o!=fa && !f[o]){
        maxs=max(maxs,siz[o]);
        dfs_root(o,tot,x);
    }
    if (maxs<mins){
        mins=maxs;
        root=x;
    }
    return;
}

int work(int x,int d) {
    //返回以 x 为根的子树内长度小于等于 K 的路径数(两个端点都在子树内) 
    //其实 d 在这里用处只有一个,是在做减法时方便把重心的儿子节点的 dis 先弄好,你也可以在分治的时候弄,不过就稍微有点麻烦了 
    cnt=0;
    get_dis(x,d,0);
    sort(dis+1,dis+cnt+1);
    int daan=0,i=1,j=cnt; 
    while (i<j){
        while (i<j && dis[i]+dis[j]>K) j--;
        daan+=j-i;  //相当于选一条路径 i,另一条可以为 [i+1,j] 里任意一条路径,这样得到的两个点之间长度(经过重心的那条路径)肯定是小于等于 K 的 
        i++;
    }
    return daan;
}

void dfs(int x){    //以 x 为重心分治一下 
    cnt=0;
    mins=Max;
    get_size(x,0);
    dfs_root(x,siz[x],0);
    ans+=work(root,0);
    f[root]=1;
    For(root) if (!f[o]){       //注意这里是以重心开始 
        ans-=work(o,W[h]);      //注意,这里 dis[o] 要先赋成 W[h](即它到重心的距离) 
        dfs(o);
    }
    return;
}

int main(){
    while(scanf("%d%d",&N,&K)!=EOF && N && K){
        Input;
        dfs(1);
        printf("%d\n",ans);
                
        num=ans=0;
        for (int i=1; i<=N; i++) Head[i]=f[i]=dis[i]=0;
    }
    return 0;
}

时间复杂度

  • 我们注意到,对于一棵大小为 n 的树,重心选好后,它的分支大小都是不大于 \(\frac{n}{2} \\) 的,那么每次分治后,联通块的大小会至少减少一半,因此最多递归分治 $\log_2 n $ 层,每次分治都会把整棵树的点都讨论一次,于是总的时间复杂度就是:
    \[ n\log_2n \ \ \ \]

提示

  • 开始我写的时候sort的边界弄错了,结果一直WA,要注意一下这些细节问题。

转载于:https://www.cnblogs.com/hehepig/p/6685546.html

相关文章:

  • 分库分表中间件特性分析
  • 百科知识 scm文件如何打开
  • PHP之旅3 php数组以及遍历数组 以及each() list() foreach()
  • Spring配置activemq异步消息监听器
  • HTML起步——学习2
  • 1.Zabbix 3.0 基础
  • bzoj4823[CQOI2017]老C的方块
  • 工资计算(用SQL来计算)
  • 电梯演说模板练习
  • 优达学城-并行编程-Unit2 硬件内存
  • ajax,json
  • 修饰符
  • 网络中数据的传输过程
  • 如何把你的Windows PC变成瘦客户机
  • codevs 1086 栈(Catalan数)
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • Angularjs之国际化
  • ES6之路之模块详解
  • Java反射-动态类加载和重新加载
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • MYSQL 的 IF 函数
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • SpiderData 2019年2月25日 DApp数据排行榜
  • 第13期 DApp 榜单 :来,吃我这波安利
  • 精彩代码 vue.js
  • 如何使用 OAuth 2.0 将 LinkedIn 集成入 iOS 应用
  • 深度解析利用ES6进行Promise封装总结
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 小程序button引导用户授权
  • 一道闭包题引发的思考
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • ​LeetCode解法汇总1276. 不浪费原料的汉堡制作方案
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (二)pulsar安装在独立的docker中,python测试
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))
  • (心得)获取一个数二进制序列中所有的偶数位和奇数位, 分别输出二进制序列。
  • (转)scrum常见工具列表
  • (转)为C# Windows服务添加安装程序
  • (转载)hibernate缓存
  • (转载)Linux网络编程入门
  • .dwp和.webpart的区别
  • .NET delegate 委托 、 Event 事件,接口回调
  • .NET连接MongoDB数据库实例教程
  • .net流程开发平台的一些难点(1)
  • .secret勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复
  • //解决validator验证插件多个name相同只验证第一的问题
  • /dev/sda2 is mounted; will not make a filesystem here!
  • @Transactional注解下,循环取序列的值,但得到的值都相同的问题
  • [ Linux ] git工具的基本使用(仓库的构建,提交)
  • [ Linux 长征路第五篇 ] make/Makefile Linux项目自动化创建工具
  • [2008][note]腔内级联拉曼发射的,二极管泵浦多频调Q laser——