0x45 点分治

0x45 点分治

到目前为止,我们用数据结构处理的大多是序列上的问题。这些问题的形式一般是给定序列中的两个位置 l l l r r r,在区间 [ l , r ] [l,r] [l,r]上执行查询或修改指令。如果给定一棵树,以及树上两个节点 x x x y y y,那么与“序列上的区间”相对应的就是“树上两点之间的路径”。我们先不考虑对路径进行修改的的操作。本节中介绍的点分治就是在一棵树上,对具有某些限定条件的路径静态进行统计的算法。

点分治是一种解决树上统计问题的常用方法,本质思想就是选择一点(重心)作为分治中心,将原问题划分为几个相同的子树上的问题,进行递归解决。

给一颗有 N N N个点的树,每条边都有一个权值。树上两个节点 x x x y y y之间的路径长度就是路径上各条边的权值之和。求长度不超过 K K K的路径有多少条。

本题中的边是无向的,即这棵树是一个由 N N N个点、 N − 1 N-1 N1条边构成的无向连通图。我们把这种树称为“无根树”(所需维护的信息与根节点是谁无关),也就是说可以任意指定一个节点为根节点,而不影响问题的答案。

若指定节点 p p p为根,则对 p p p而言,树上的路径可以分为两类:

1.经过根节点 p p p(包含一端为根节点 p p p)。

2.包含于 p p p的某一棵子树中(不经过根节点)。

根据分治的思想,对于第2类路径,显然可以把 p p p的每棵子树作为子问题,递归进行处理。

而对于第1类路径,可以从根节点 p p p分成“ x ∼ p x\sim p xp”与“ p ∼ y p\sim y py”两段。回顾在0x21节所学到的知识,我们可以从 p p p出发对整棵树进行DFS,求出数组 d d d其中 d [ x ] d[x] d[x]表示点 x x x到根节点 p p p的距离。同时还可以求出数组 b b b其中 b [ x ] b[x] b[x]表示点 x x x属于根节点 p p p的哪一棵子树,特别的,令 b [ p ] = p b[p]=p b[p]=p

此时满足题目要求的第1类路径满足以下两个条件的点对 ( x , y ) (x,y) (x,y)的个数:

1. b [ x ] ≠ b [ y ] b[x]\neq b[y] b[x]=b[y]

2. d [ x ] + d [ y ] ≤ K d[x]+d[y]\leq K d[x]+d[y]K。如下图所示。

在这里插入图片描述

定义 C a l ( p ) Cal(p) Cal(p)表示在以 p p p为根的树中统计上述点对的个数(第1类路径的条数)。 C a l ( p ) Cal(p) Cal(p)有两种常见的实现方式。针对不同的题目,二者各有优劣。

方法一:树上直接统计

p p p的子树为 s 1 , s 2 , . . . , s m s_1,s_2,...,s_m s1,s2,...,sm

对于 s i s_i si中每个节点 x x x,把在子树 s 1 , s 2 , . . . , s i − 1 s_1,s_2,...,s_{i-1} s1,s2,...,si1中满足 d [ x ] + d [ y ] ≤ K d[x]+d[y]\leq K d[x]+d[y]K的节点 y y y的个数累加到答案中即可。

具体来说,可以建立一个树状数组,依次处理每棵子树 s i s_i si

1.对于 s i s_i si中的每个节点 x x x,查询前缀和 a s k ( K − d [ x ] ) ask(K-d[x]) ask(Kd[x]),即为所求的 y y y的个数。

2.对于 s i s_i si中的每个节点 x x x,执行 a d d ( d [ x ] , 1 ) add(d[x],1) add(d[x],1),表示与 p p p距离为 d [ x ] d[x] d[x]的节点增加了1个。

按子树一棵棵进行处理保证了 b [ x ] ≠ b [ y ] b[x]\neq b[y] b[x]=b[y],查询前缀和保证了 d [ x ] + d [ y ] ≤ K d[x]+d[y]\leq K d[x]+d[y]K

需要注意的是,树状数组的范围与路径长度有关,这个范围远比 N N N要大。而本题中不易进行离散化。一种解决方案是用平衡树代替树状数组,以保证 O ( N l o g N ) O(NlogN) O(NlogN)的复杂度,但代码复杂度显著增加。所以本题更适用下一种方法。

方法二:指针扫描数组

把树中每个点放进一个数组 a a a,并把数组 a a a按照节点的 d d d值排序。

使用两个指针 L , R L,R L,R分别从前、后开始扫描 a a a数组。

容易发现,在指针 L L L从左往右扫描的过程中,恰好使得 d [ a [ L ] ] + d [ a [ R ] ] ≤ K d[a[L]]+d[a[R]]\leq K d[a[L]]+d[a[R]]K的指针 R R R的范围是从右往左单调递减的。

另外,我们用数组 c n t [ s ] cnt[s] cnt[s]维护在 L + 1 L+1 L+1 R R R之间满足 b [ a [ i ] ] = s b[a[i]]=s b[a[i]]=s的位置 i i i的个数。

于是,当路径的一端 x x x等于 a [ L ] a[L] a[L]时,满足题目要求的路径另一端 y y y的个数就是 R − L − c n t [ b [ a [ L ] ] ] R-L-cnt[b[a[L]]] RLcnt[b[a[L]]]

总而言之,整个点分治算法的过程就是:

1.任选一个根节点 p p p(后面我们将说明, p p p应该取树的重心)。

2.从 p p p出发进行一次DFS,求出 d d d数组和 b b b数组。

3.执行 C a l ( p ) Cal(p) Cal(p)

4.删除根节点 p p p,对 p p p的每棵子树(看作无根树)递归执行1~4步。

在点分治过程中,每一层的所有递归过程合计对每个点处理1次。因此,若递归最深处到达第 T T T层,整个算法的时间复杂度为 O ( T N l o g N ) O(TNlogN) O(TNlogN)

如果问题中的树是一条链,最坏情况下每次都以链的一端为根,那么点分治将需要递归 N N N层,时间复杂度退化到 O ( N 2 l o g N ) O(N^2logN) O(N2logN)。为了避免这种情况,我们每次选择树的重心(曾在0x21节提及)作为根节点 p p p。对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心。而不难证明树的重心具有以下性质:以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。

点分治就至多递归 O ( l o g N ) O(logN) O(logN)层,算法的时间复杂度为 O ( N l o g 2 N ) O(Nlog^2N) O(Nlog2N)。如下图所示。

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;const int SIZE=1e4+5;
int N,K,tot,w,sum,cnt,ans;
int ver[SIZE*2],edge[SIZE*2],nex[SIZE*2],head[SIZE];
int max_part[SIZE],siz[SIZE],dis[SIZE],root[SIZE],rec[SIZE],point[SIZE];
bool del[SIZE];inline int read()
{int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();return x*f;
}inline void add(int x,int y,int z)
{ver[++tot]=y,edge[tot]=z;nex[tot]=head[x],head[x]=tot;
}void dfs_w(int x,int fa)
{siz[x]=1,max_part[x]=0;for(int i=head[x];i;i=nex[i]){   int y=ver[i];if(y==fa||del[y]) continue;dfs_w(y,x);siz[x]+=siz[y];max_part[x]=max(max_part[x],siz[y]);}max_part[x]=max(max_part[x],sum-siz[x]);if(max_part[x]<max_part[w])w=x;
}void dfs(int x,int fa)
{point[++cnt]=x,siz[x]=1;for(int i=head[x];i;i=nex[i]){int y=ver[i],z=edge[i];if(y==fa||del[y]) continue;if(x==w) root[y]=y;else root[y]=root[x];rec[root[y]]++;dis[y]=dis[x]+z;dfs(y,x);siz[x]+=siz[y];}
}void solve(int x,int fa)
{dfs_w(x,fa);dis[w]=0;root[w]=w;rec[w]=1;for(int i=head[w];i;i=nex[i]){int y=ver[i];rec[y]=0;}cnt=0;dfs(w,0);sort(point+1,point+cnt+1,[](int x,int y){return dis[x]<dis[y];});int L=1,R=cnt;rec[root[point[L]]]--;while(L<R){if(dis[point[L]]+dis[point[R]]>K){rec[root[point[R]]]--;R--;}else{ans+=R-L-rec[root[point[L]]];L++;rec[root[point[L]]]--;}}del[w]=true;for(int i=head[w];i;i=nex[i]){int y=ver[i];if(y==fa||del[y]) continue;sum=siz[y],w=0,max_part[0]=0x3f3f3f3f;solve(y,w);}
}int main()
{N=read();K=read();while(N||K){tot=0;for(int i=1;i<=N;++i) head[i]=0,del[i]=false;int x,y,z;for(int i=1;i<N;++i){x=read();y=read();z=read();x++,y++;add(x,y,z);add(y,x,z);}ans=0;sum=N,w=0,max_part[0]=0x3f3f3f3f;solve(1,0);printf("%d\n",ans);N=read();K=read();}return 0;
}

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

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

相关文章

jQuery日历签到插件下载

jQuery日历签到插件下载-遇见你与你分享

记一次JSF异步调用引起的接口可用率降低 | 京东云技术团队

前言 本文记录了由于JSF异步调用超时引起的接口可用率降低问题的排查过程&#xff0c;主要介绍了排查思路和JSF异步调用的流程&#xff0c;希望可以帮助大家了解JSF的异步调用原理以及提供一些问题排查思路。本文分析的JSF源码是基于JSF 1,7.5-HOTFIX-T6版本。 起因 问题背景…

TCP/UDP协议

1. 请解释TCP和UDP的主要区别。 TCP和UDP都是位于传输层的协议&#xff0c;具有不同的特点和应用场景。以下是它们的主要区别&#xff1a; 连接方式&#xff1a;TCP是面向连接的协议&#xff0c;这意味着在数据传输之前需要先建立连接。这通常通过三次握手来建立连接&#xff…

【Java】SpringBoot快速整合WebSocket实现客户端服务端相互推送信息

目录 什么是webSocket&#xff1f; webSocket可以用来做什么? WebSocket操作类 一&#xff1a;测试客户端向服务端推送消息 1.启动SpringBoot项目 2.打开网站 3.进行测试消息推送 4.后端进行查看测试结果 二&#xff1a;测试服务端向客户端推送消息 1.接口代码 2.使…

04-获取认证的用户身份信息

存储用户信息的方式 获取用户信息的流程 用户提交账号和密码后,DaoAuthenticationProvider调用UserDetailsService接口实现类的loadUserByUsername()方法,该方法可以接收请求参数username的值,然后根据该值查询用户信息,最后将账号,密码,权限封装到UserDetails对象中并返回给…

本地映射测试环境域名,解决登录测试环境后,也可以使用本地域名访问,可以正常跑本地项目

问题&#xff1a;单点登录进入系统不使用token&#xff0c;是将token携带在cookie中&#xff0c;登录成功后每次调用接口&#xff0c;都会在cookie中自动携带&#xff0c;这样导致即使在本地使用proxy代理解决了跨域&#xff0c;但由于本地域名不一致&#xff0c;也无法进行本地…

HW01—SY系列单点测厚仪 应用于轧钢橡胶、金属、塑料…

关键字&#xff1a;单点测厚仪,HW01—SY测厚仪,常温测厚仪,热轧板测厚仪,非接触测厚仪,橡胶测厚仪,塑料板测厚仪,木板测厚仪, 产品简介&#xff1a; 该系列测厚仪整体采用C型架结构&#xff0c;C型架上安装上下两个对射的激光位移传感器&#xff0c;可测量一个点的厚度尺寸。C型…

杰发科技AC7840——EEPROM初探

0.序 7840和7801的模拟EEPROM使用不太一样 1.现象 按照官方Demo&#xff0c;在这样的配置下&#xff0c;我们看到存储是这样的&#xff08;连续三个数字1 2 3&#xff09;。 使用串口工具的多帧发送功能 看不出多少规律 修改代码后 发现如下规律&#xff1a; 前四个字节是…

信号与线性系统翻转课堂笔记16——离散LTI系统的各类响应

信号与线性系统翻转课堂笔记16——离散LTI系统的各类响应 The Flipped Classroom16 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、要点 &#xff08;1&#xff0c;重点…

Java连接Mysql报错:javax.net.ssl.SSLException: Received fatal alert: internal_error

大致报错日志如下&#xff1a; The last packet successfully received from the server was 11 milliseconds ago. The last packet sent successfully to the server was 10 milliseconds ago.at sun.reflect.GeneratedConstructorAccessor275.newInstance(Unknown Source)…

了解 NSA 关于管理 OSS 和 SBOM 的最新指南

开源软件很容易受到恶意行为者的攻击&#xff0c;但软件材料清单可以帮助减轻威胁。美国国家安全局的指导为管理生态系统奠定了坚实的基础。 软件供应链安全仍然是网络安全和软件行业的一个关键话题&#xff0c;并且有充分的理由&#xff0c;从针对大型软件供应商的持续攻击到…

简单工厂、工厂方法、抽象工厂和策略模式

摘要 本文简单介绍软件开发过程中面临的痛点和几个总体原则。详细介绍了简单工厂、工厂方法、抽象工厂和策略模式的实现&#xff0c;以及各种模式之间的相似、区别。 背景 开发面临哪些问题&#xff08;痛点&#xff09;&#xff1f; 相信做过大型软件开发的tx都遇到过以下类似…

2023年年终总结 —— 致满载荣誉或满脸惆怅的你

开篇请允许我引用歌曲《西楼儿女》的一句歌词&#xff1a; 陌生的朋友你请听我讲&#xff0c;许多年前我也曾有梦想&#xff0c;想过满载荣誉回到家乡&#xff0c;这肆意的风压弯了海棠。 2023年即将过去&#xff0c;不管你这一年经历了多少&#xff0c;或是职场的得心应手、情…

微服务事务处理:CAP 定理和最终一致性的关系

CAP 定理和最终一致性 CAP 定理和最终一致性是两个密切相关的概念&#xff0c;但它们在范围和细节上有所不同。以下是比较&#xff1a; CAP 定理 **正式陈述&#xff1a;**在分布式系统中&#xff0c;最多只能同时满足以下三个保证中的两个&#xff1a;一致性、可用性和分区…

STL——遍历算法

1.for_each 函数原型&#xff1a; for_each(iterator beg, iterator end, _func);——// 遍历算法 遍历容器元素&#xff1b; beg 开始迭代器&#xff1b;end 结束迭代器&#xff1b; _func 函数或者函数对象 #include<iostream> using namespace std; #include<ve…

C++ 判断 float 或 double 数据小数点后位数的方法

文章目录 Method 1Method 2Method 3 Method 1 #include <cmath> int countDecimalPlaces(double num) { if (num 0) return 0; // 避免除以零错误 return std::floor(std::log10(std::abs(num - std::floor(num)))) 1; }Method 2 #include <string> #…

GBASE南大通用-小内存单机安装GBase 8c分布式数据库实践

* 这种小内存部署方式仅用于分布式数据库个人学习使用&#xff0c;不建议用于其他用途。 随着数据高并发复杂场景业务需求不断增多&#xff0c;信息数据呈现出爆炸式增长、多源多维、数据类型繁复等特征。在这一趋势下&#xff0c;目前分布式数据库因其架构的天然优势&#xf…

Creo各版本安装指南

下载链接 https://pan.baidu.com/s/1VyP0_185mJeBiorlpUJqrQ?pwd0531 1.鼠标右击【Creo10.0(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 Creo10.0(64bit)】。 ​2.打开解压后的文件夹&#xff0c;鼠标右击【Crack】选择【解压到…

mysql 数据查重与查重分页

起因是公司的crm录入不规范,有重复数据。 之后考虑到需要手动处理,首先需要自动找出重复的数据 查重要求: 存在多个不允许重复的字段,任一字段重复,则判断为同一个客户。划分到同一重复组中。 查重sql如下 SELECT CONCAT(组, dense_rank() OVER (ORDER BY group_key)) …

Winform RDLC报表(数据库连接、报表函数使用、动态表头)

文章目录 NuGet安装库数据库连接报表设计报表引用添加报表 数据集设计方法一手动添加方法二——连接数据库添加 关联报表与数据集表格数据与数据集数据设计表格格式、字体设计报表数据字段绑定 Winform 使用报表控件数据库填充数据集从数据库获取与数据源相同字段的数据 动态表…