树上操作【点分治】 - 原理 中心分解 【POJ No. 1741】 树上两点之间的路径数 Tree

news2024/11/28 14:34:31

树上操作【点分治】 - 原理 中心分解

分治法指将规模较大的问题分解为规模较小的子问题,解决各个子问题后合并得到原问题的答案。树上的分治算法分为点分治和边分治。

点分治经常用于带权树上的路径统计,本质上是一种带优化的暴力算法,并融入了容斥原理。对树上的路径,并不要求这棵树是有根树,无根树不影响统计结果。

分治法的核心是分解和治理。那么如何分?如何治?

数列上的分治法,通常从数列中间进行二等分,也就是说分解得到的两个子问题规模相当。若将n 个数分解为1、n -1,则分治法会退化为暴力穷举,那么对树怎么划分呢?

对树的划分要尽量均衡,不要出现一个子问题太大,另一个子问题太小的情况。也就是说,期望划分后每棵子树的节点数都不超过n /2。那么选择哪个节点作为划分点呢?可以选择树的重心。

树的重心指删除该节点后得到的最大子树的节点数最少

定理: 删除重心后得到的所有子树,其节点数必然不超过n /2。

证明: 若s 为树的重心,则删除s 后得到的最大子树T 1 节点数最少。假设T 1 节点数m >n /2,则以s 为根的子树节点数<n /2。若选择t作为重心,则得到的最大子树T 2 的节点数为m -1(m >n /2),很明显,T 2 <T 1 ,删除s 后得到的最大子树T 1 的节点数显然不是最少的,这与“s 是树的重心”矛盾。

在这里插入图片描述

因此以树的重心作为划分点,每次划分后得到的子树大小减半,所以递归树的高度为O (logn )。

【POJ No. 1741】 树上两点之间的路径数 Tree

北大OJ 题目地址

在这里插入图片描述

【题意】

一棵有n 个节点的树,每条边都有一个长度(小于1001的正整数),dist(u , v )为节点u 和v 的最小距离。给定一个整数k ,对每对节点(u , v ),当且仅当dist(u , v )不超过k 时才叫作有效。计算给定的树中有多少对节点是有效的。

【输入输出】

输入:

输入包含几个测试用例。每个测试用例的第1行都包含两个整数n 、k (n ≤10000),下面的n -1行,每行都包含三个整数u 、v、l ,表示节点u 和v 之间有一条长度为l 的边。在最后一个测试用例后面跟着两个0。

输出:

对每个测试用例,都单行输出答案。

【样例】

在这里插入图片描述

【思路分析】

根据测试用例的输入数据,树形结构如下图所示。树中距离不超过4的有8对节点:1-2、1-3、1-4、1-5、2-3、3-4、3-5、4-5。

在这里插入图片描述
查询树中有多少对节点距离不超过k ,相当于查询树上两点之间距离不超过k 的路径有多少条。可采用点分治解决。当数据量很大时,树上两点之间的路径很多,采用暴力穷举的方法是不可行的,可以采用树上分治算法进行点分治。

以树的重心root为划分点,则树上两点u 、v的路径分为两种:①经过root;②不经过root(两点均在root的一棵子树中),只需求解第1类路径,对第2类路径根据分治策略继续采用重心分解即可得到。

在这里插入图片描述

【算法设计】

① 求树的重心root。

② 从树的重心root出发,统计每个节点到root的距离。

③ 对距离数组排序,以双指针扫描,统计以root为根的子树中满足条件的节点数。

④ 对root的每一棵子树v 都减去重复统计的节点数。

⑤ 从v 出发重复上述过程。

【举个栗子】

一棵树如下图所示,求解树上两点之间距离(路径长度)不超过4的路径数。

在这里插入图片描述

① 求解树的重心,root=1。

② 从树的重心root出发,统计每个节点到root的距离,得到距离数组dep[]。

在这里插入图片描述

③ 对距离数组进行非递减排序,结果如下图所示。然后以双指针扫描,统计以root为根的子树中满足条件的节点数。

在这里插入图片描述

  • L =1,R =7,若dep[L ]+dep[R ]>4,则R --。
    在这里插入图片描述

  • L =1,R =5,dep[L ]+dep[R ]<=4,则ans+=R -L =4,L ++。
    在这里插入图片描述

为什么这么计算呢?因为序列从右向左递减,当dep[L ]+dep[R]≤4时,[L , R ]区间的其他节点与dep[L ]的和值必然也小于或等于4,该区间的节点个数为R -L ,累加即可。

  • L =2,R =5,若dep[L ]+dep[R ]≤4,则ans+=R -L =7,L ++。
    在这里插入图片描述

  • L =3,R =5,若dep[L ]+dep[R ]>4,则R --。
    在这里插入图片描述

  • L =3,R =4,若dep[L ]+dep[R ]≤4,则ans+=R -L =8,L ++,此时L =R ,算法停止。

在这里插入图片描述

也就是说,以1为根的树,满足条件的路径数有8个。在这些路径中,有些是合并路径,例如两条路径1-2和1-3,其路径长度之和为4,满足条件。这相当于将两条路径合并为2-1-3,路径长度为4。

在这里插入图片描述
路径长度小于或等于4的8条路径如下表所示。

在这里插入图片描述

第7条路径的合并是错误的。路径1-3和路径1-3-7的路径长度之和虽然小于或等于4,但是不可以作为合并路径,因为树中任意两个节点之间的路径都是不重复的。而路径1-3和路径1-3-7之间的路径有重复,所以这样的路径不可以作为合并路径。可以先统计该路径,然后在处理以3为根的子树时去重。

④ 对root的每一棵子树v 都先去重,然后求以v 为根的子树的重心,重复上述过程。

在这里插入图片描述

⑤ 去重。以2为根的子树没有重复统计的路径。

在这里插入图片描述

⑥ 以2为根的子树的重心为2,该子树满足条件的路径有3条,ans+=3=11,这3条为2-5、2-6、2-5, 2-6(相当于一条合并路径5-2-6,路径长度为4)。

⑦ 去重。以3为根的子树,该子树有一条重复统计的路径(1-3和1-3-7的合并路径)。减去重复路径,ans-1=10。

在这里插入图片描述

⑧ 以3为根的子树的重心为3,该子树满足条件的路径有1条(3-7),路径长度为1,ans+1=11。

⑨ 以4为根的子树的重心为4,该子树没有重复统计的路径,也没有满足条件的路径。

【算法实现】

① 求树的重心。只需进行一次深度优先遍历,找到删除该节点后最大子树最小的节点。用f [u ]表示删除u 后最大子树的大小,size[u ]表示以u 为根的子树的节点数,S 表示整棵子树的节点数。先统计u 的所有子树中最大子树的节点数f [u ],然后与S -size[u ]比较,取最大值。

在这里插入图片描述
若f [u ]<f [root],则更新当前树的重心为root=u 。

② 统计每个节点到重心u 的距离。把dep[0]当作计数器使用,初始化为0,深度优先遍历,将每个节点到u 的距离d[]都存入dep数组中。

③ 统计重心u 的子树中满足条件的个数。初始化d[u ]=dis且dep[0]=0(用于计数),将每个节点到u 的距离d[]都存入dep数组中;然后对dep数组排序,L =1,R =dep[0](dep数组末尾的下标),用sum累加满足条件的节点对个数。

④ 对重心u 的所有子树都先去重,然后递归求解答案。对u 的每一棵子树v 都减去v 中重复统计的答案,然后从v 出发重复上述过程。

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int maxn=10005;
int cnt,n,k,ans,head[maxn];
int root,S,size[maxn],f[maxn],d[maxn],dep[maxn];
bool vis[maxn];

struct edge
{
	int to,next,w;
}edge[maxn*2];

void add(int u,int v,int w)
{
	edge[++cnt].to=v;
	edge[cnt].w=w;
	edge[cnt].next=head[u];
	head[u]=cnt;
}

void getroot(int u,int fa)//获取重心
{
    size[u]=1;
	f[u]=0;//删除u后,最大子树的大小 
    for(int i=head[u];i;i=edge[i].next)
    {
    	int v=edge[i].to;
    	if(v!=fa&&!vis[v])
        {
            getroot(v,u);
            size[u]+=size[v];
       		f[u]=max(f[u],size[v]);
        }
	}    
    f[u]=max(f[u],S-size[u]);//S为当前子树总结点数 
    if(f[u]<f[root])
		root=u;
}

void getdep(int u,int fa)//获取距离
{
    dep[++dep[0]]=d[u];//保存距离数组 
    for(int i=head[u];i;i=edge[i].next)
    {
    	int v=edge[i].to;
    	if(v!=fa&&!vis[v])
    	{
    		d[v]=d[u]+edge[i].w;
            getdep(v,u);
		}
    }
}

int getsum(int u,int dis) //获取u的子树中满足个数
{
    d[u]=dis;
	dep[0]=0;
	getdep(u,0);
    sort(dep+1,dep+1+dep[0]);
    int L=1,R=dep[0],sum=0;
    while(L<R)
		if(dep[L]+dep[R]<=k)
			sum+=R-L,L++;
		else
			R--;
    return sum;
}

void solve(int u) //获取答案
{
    vis[u]=true;
	ans+=getsum(u,0);
    for(int i=head[u];i;i=edge[i].next)
    {
    	int v=edge[i].to;
		if(!vis[v])
		{
			ans-=getsum(v,edge[i].w);//减去重复
            root=0;
			S=size[v];
			getroot(v,u);
            solve(root);
		}
    }
}

int main()
{
	
    f[0]=0x7fffffff;//初始化树根 
	while(scanf("%d%d",&n,&k),n+k)
    {
        memset(vis,0,sizeof(vis));
		memset(head,0,sizeof(head));
		cnt=0;ans=0;
        for(int i=1;i<=n-1;i++)
        {
            int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
			add(y,x,z);
        }
        root=0;
		S=n;
		getroot(1,0);
		solve(root);
        printf("%d\n",ans);
    }
	
    return 0;
}

在这里插入图片描述

【算法分析】

因为每次都选择树的重心作为划分点,点分治至多递归O (logn )层,dep[]排序的时间复杂度为O (n logn ),因此总的时间复杂度为O(n log^2 n )。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/88182.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【内网安全-基础】基础知识、信息收集、工具

目录 一、基础知识 1、内网&#xff1a; 2、工作组&#xff1a; 3、域(Domain)&#xff1a; 二、基础信息收集 1、判断是否在域内 2、机器角色判断 3、出网协议判断 4、端口判断 三、常规信息收集 1、常用命令 2、常用命令 3、工具&插件 LadonGO CS插件 Adfi…

基于Java(Spring+Struts+Hibernate 框架)实现(Web)学生课程管理系统【100010038】

课程管理系统设计文档 二、引言 2.1 目的 ​ 本文档详细描述了课程管理系统的设计&#xff0c;达到引导开发的作用&#xff0c;同时实现测试人员以及用户的沟通。 ​ 本文档面向开发人员&#xff0c;测试人员以及最终用户编写&#xff0c;是了解系统的导航。 2.2 范围 ​…

Win10系统电脑连接打印机的操作方法教学

Win10系统电脑连接打印机的操作方法教学分享&#xff0c;很多用户在办公的时候都会需要使用到打印机。用用户自己购买了打印机之后&#xff0c;不懂怎么去连接自己的电脑来进行使用的方法&#xff0c;接下来我们一起来看看Win10系统电脑连接打印机的操作方法分享吧。 Win10连接…

2022职场人状态和顺风出行感受调研报告

2022年即将过去&#xff0c;作为职场人的你会如何总结&#xff1f;职场同路人又有哪些想对彼此说的话&#xff1f;近日&#xff0c;嘀嗒出行发布《2022职场人状态和顺风出行感受调研报告》&#xff0c;基于近8000名嘀嗒顺风车车主和乘客分享各自职场经历和顺风出行感受&#xf…

运维人必须掌握的 5 种常用运维监控工具

运维监控工具千千万&#xff0c;仅开源的解决方案就有流量监控&#xff08;MRTG、Cacti、SmokePing、Graphite 等&#xff09;和性能告警&#xff08;Nagios、Zabbix、Zenoss Core、Ganglia、OpenTSDB等&#xff09;可供选择。 并且每种软件都有自己的特点和功能&#xff0c;各…

WiFi热点加装短信认证怎么操作?

公共场所提供无线wifi上网服务&#xff0c;需对用户进行实名认证。手机短信实名认证以其用户体验、综合成本等优势&#xff0c;成为公共wifi上网认证的首选方案。 无线wifi上网实现信认证功能&#xff0c;需要借助上网行为管理设备搭配验证短信平台使用&#xff1b;根据无线wi…

基于Java(Jsp+Sevlet)+MySql 实现的(Web)成绩管理系统【100010041】

1 概述 1.1 开发背景 随着学生数量的日渐增多&#xff0c;学生教务系统的数据量也不断增加&#xff0c;这无疑大大增加了教务系统的负担。如果能把负责学生成绩管理的模块独立出来形成一个独立的系统&#xff0c;便可以有效降低教务系统的数据量&#xff0c;不仅可以方便管理…

阿里巴巴Java开发手册(黄山版)

阿里巴巴 Java 开发手册&#xff08;黄山版&#xff09; 链接&#xff1a;https://pan.baidu.com/s/1iKsXlq1DSbePLvuysYbA4A 提取码&#xff1a;yyds 阿里巴巴将 Java 开发手册 从 1.7.0 的嵩山版更新至 1.7.1 的黄山版&#xff0c;新增 11 条新规约&#xff0c;具体变动如下…

[ Linux ] 线程控制(线程创建,等待,终止)

在上一篇我们了解了Linux下线程的相关概念。而本篇的主要内容是线程控制。线程控制包括线程的创建&#xff0c;线程的终止&#xff0c;线程等待等问题&#xff0c;以及线程分离和Linux常见线程安全问题。 目录 1.线程控制 1.1POSIX线程库 1.2 创建线程 1.2.1 创建线程编码…

新库上线 | CnOpenData劳务外包企业工商注册基本信息数据

劳务外包企业工商注册基本信息数据 一、数据简介 随着我国社会主义市场经济的发展&#xff0c;劳务市场中的用工方式也朝着多样化方向演变&#xff0c;劳务外包正是现代化人力资源管理和企业生产实际结合的一种独特的新模式。 在劳务外包过程中&#xff0c;企业将人事管理的部…

Node 文件查找优先级及 Require 方法文件查找策略

Node 文件查找优先级及 Require 方法文件查找策略 一、模块规范 NodeJS对CommonJS进行了支持和实现&#xff0c;让我们在开发node的过程中可以方便的进行模块化开发&#xff1a; 在Node中每一个js文件都是一个单独的模块模块中包括CommonJS规范的核心变量&#xff1a;export…

图数据库知识点2:图思维方式

在上一个知识点中&#xff0c;我们剖析了关系型数据库、数仓湖与图数据库的差异。在本文&#xff0c;我们会着重介绍一个重要的概念——图思维方式&#xff08;Graph-thinking&#xff09;。 关于思维方式 每个人应该如何思考&#xff0c;他/她是如何思考的&#xff0c;这是一…

朝代更替中的上下五千年

《近代中国社会的新陈代谢》 关于作者 陈旭麓是著名历史学家、华东师范大学的 建校元老之一。生前是中国史学会理事、 中国现代史学会副理事长、上海地方史志 研究会副会长。著有《初中本国史》《司马迁的历史观》《近代中国社会的新陈代谢》 《浮想录》等。 关于本书 这本…

Qt / Qml 中捕获(中文)输入法事件(按下 提交)

【写在前面】 最近工作中遇到一个奇怪的问题&#xff1a; 本来想在 TextEdit ( QTextEdit ) 中捕获一下键盘按键按下的事件。 然而&#xff0c;当输入法为英文时( 正常输入字符 )&#xff0c;可以捕获到按键事件&#xff0c;但当我切换到中文时&#xff0c;弹出输入法选框后&am…

web前端网页设计期末课程大作业:企业网页主题网站设计——舞蹈培训11页HTML+CSS+JavaScript

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

从零开始,教你写一个串口调试助手

摘要&#xff1a;相信很多小伙伴都没接触过QT&#xff0c;如果想用QT写一个调试助手&#xff0c;首先是要会一点C语法。只要能看得懂C的代码&#xff0c;就能很快的写一个串口调试助手。 下面先推荐两个视频教程&#xff0c;感兴趣的可以看一看&#xff01; 1、B站Jomse工 B站…

项目建议书怎么写?

Why 为什么需要项目建议书? 行政、国企、事业单位间的买卖需要按国家规定的“政府采购法”进行(个体户,私企间的买卖不需要按“政府采购法”进行),不是个人的消费,有一整套流程,《项目建议书》这套流程中的产物之一。 即国家规定,纳税人的钱不能乱花,买之前要想好买…

南卡和Snowkids电容笔哪款更值得入手?口碑最佳的国产电容笔

随着苹果推出了Apple Pencil之后后&#xff0c;平替电容笔的产品也随之出现了不少&#xff0c;其中大部分都是没很高知名度的牌子&#xff0c;价格也是五花八门&#xff0c;有便宜的也有昂贵的&#xff0c;让消费者有了更多的选择&#xff0c;对于第一次购买电容笔的小白来说但…

【Python】【期末复习题】

文章目录一、单选题&#xff08;20分&#xff09;二、判断题&#xff08;10分&#xff09;三、填空题&#xff08;10分&#xff09;四、问答题&#xff08;共30分&#xff0c;6题&#xff0c;每题5分&#xff09;五、程序题&#xff08;3题&#xff0c;每题10分&#xff0c;共3…

WIFI智能电子标牌的优势

一、与市面产品对比有什么优化升级? 使用环境标准wifi通信&#xff0c;避免单独安装基站&#xff0c;减少部署的麻烦。 二、刷新一次消耗多少电量&#xff0c;平均每秒消耗多少? 0.09879mAh&#xff0c;每秒平均电流105uA。 三、充满电需要多久&#xff0c;充一次电可以用…