点分治 学习笔记
0. 点分治的用途
点分治可以解决树上的关于路径的问题,例如 洛谷P4178 Tree。(题目大意:给定一棵 \(n\) 个节点的树,每条边有边权,求出树上两点距离小于等于 \(k\) 的点对数量)这道题如果使用 \(O(n^2)\) 的暴力算法 显然 会T飞 ,然而之后您就会看到,点分治算法可以在 \(O(n\log^2 n)\) 的时间复杂度内解决它。
1.思想
顾名思义,点分治使用了分治的思想,把原问题拆分成若干个子问题,分别求解后再合并。把大象装进冰箱里需要几步?
1.1. 分
注意到如果指定一个节点为根节点,那么一个路径有可能有以下两个来源:
1.路径经过根节点;
2.路径完全被子树包含。
到这里一个分治算法已经呼之欲出了——可以递归地处理第二种情况,只需要在算法中考虑第一种情况就可以了。
1.2. 治
第二种情况可以递归地处理,并且递归到叶子节点时就不需要考虑第二种情况了(根本没有子树),所以这里主要考虑第一种情况。
按顺序考虑每一个子树,用一个树状数组(或者是一个别的什么数据结构)来维护根节点到已经考虑过的每一个节点,在新加入一个子树时对于新子树的每一个节点求出有多少个根节点到“老节点”的距离小于 (\(k-\) (根节点到新节点的距离))并统计进答案,再把每一个新节点塞进树状数组里。这样就解决了第一种情况了。
最好结合代码理解:
//mark:树状数组 g:图
int dis[MAXN+5],tail;
void get_dis(int u,int fa,int now){
dis[++tail]=now;
for(int i=0;i<g[u].size();i++){
int v=g[u][i].v,w=g[u][i].w;
if(!removed[v]&&v!=fa){
get_dis(v,u,now+w);
}
}
}
int calc(int u){//处理第一种情况
int res=0;
ta=0;
for(int i=0;i<g[u].size();i++){
int v=g[u][i].v,w=g[u][i].w;
if(!removed[v]){
tail=0;
get_dis(v,u,w);
for(int j=1;j<=tail;j++){//统计
if(dis[j]<=k)res+=mark.query(k-dis[j]);
}
for(int j=1;j<=tail;j++){//加入
if(dis[j]<=k){
mark.add(dis[j],1);
res++;
vis[++ta]=dis[j];
}
}
}
}
for(int i=1;i<=ta;i++){//复原树状数组方便下次使用
if(vis[i]<=k){
mark.add(vis[i],-1);
}
}
return res;
}
1.3. 合
只需要无脑地把每种情况加起来就可以了(
1.4. “细节”
在递归时一定要用子树的重心作为根节点,这样才能保证时间复杂度最优。
至于证明,关于此,我确信已发现了一种美妙的证法 ,可惜这里空白的地方太小,写不下。 其实是我太菜了不会证TwT 可以感性理解一下,根节点取重心可以使问题分割地尽可能均匀,分治就跑得飞快了。
2. 时间复杂度
据说是 \(O(n\log^2 n)\) 的,然而我不会证啊qaq
如果递归时根节点不取重心,时间复杂度会退化为 \(O(n^2\log n)\),还不如暴力。
3. Code
#include <bits/stdc++.h>
using namespace std;
#define MAXN 40000
#define MAXW 1000
#define INF 0x3fffffff
struct BIT{//一般通过树状数组
int tree[MAXN*MAXW+5];
int query(int x){
int res=0;
while(x){
res+=tree[x];
x-=x&-x;
}
return res;
}
void add(int x,int k){
while(x<=MAXN*MAXW+2){
tree[x]+=k;
x+=x&-x;
}
}
};
int n,k;
struct edge{
int v,w;
edge(){v=w=0;}
edge(int _v,int _w){v=_v;w=_w;}
};
vector<edge> g[MAXN+5];
bool removed[MAXN+5];//由于每个点在递归之后就没用了,所以要打个标记把它移除掉
int zx,zx_maxx=INF,siz[MAXN+5];
void get_zx(int u,int fa,int tot){//求重心
siz[u]=1;
int maxx=0;
for(int i=0;i<g[u].size();i++){
int v=g[u][i].v;
if(!removed[v]&&v!=fa){
get_zx(v,u,tot);
siz[u]+=siz[v];
maxx=max(maxx,siz[v]);
}
}
maxx=max(maxx,tot-siz[u]);
if(zx_maxx>maxx){
zx=u;
zx_maxx=maxx;
}
}
int dis[MAXN+5],tail;
void get_dis(int u,int fa,int now){//求节点到每一个其他点的距离
dis[++tail]=now;
for(int i=0;i<g[u].size();i++){
int v=g[u][i].v,w=g[u][i].w;
if(!removed[v]&&v!=fa){
get_dis(v,u,now+w);
}
}
}
BIT mark;
int vis[MAXN+5],ta;
int calc(int u){//求当前子树经过根节点的合法路径数
int res=0;
ta=0;
for(int i=0;i<g[u].size();i++){
int v=g[u][i].v,w=g[u][i].w;
if(!removed[v]){
tail=0;
get_dis(v,u,w);
for(int j=1;j<=tail;j++){
if(dis[j]<=k)res+=mark.query(k-dis[j]);
}
for(int j=1;j<=tail;j++){
if(dis[j]<=k){
mark.add(dis[j],1);
res++;
vis[++ta]=dis[j];
}
}
}
}
for(int i=1;i<=ta;i++){
if(vis[i]<=k){
mark.add(vis[i],-1);
}
}
return res;
}
int work(int u){//分治
removed[u]=true;
int res=calc(u);
//cout<<u<<" "<<res<<endl;
for(int i=0;i<g[u].size();i++){
int v=g[u][i].v,w=g[u][i].w;
if(!removed[v]){
zx_maxx=INF;
get_zx(v,0,siz[v]);
res+=work(zx);
}
}
return res;
}
int main(){
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n-1;i++){
int u,v,w;
cin>>u>>v>>w;
g[u].push_back(edge(v,w));
g[v].push_back(edge(u,w));
}
cin>>k;
zx_maxx=INF;
get_zx(1,0,n);
cout<<work(zx)<<endl;
return 0;
}
4. 点一个赞!
本文作者:ztxcsl,使用署名-非商业性使用-相同方式共享 4.0 国际
原文地址:https://www.cnblogs.com/ztxcsl/p/15128323.html
- WCF后续之旅(6): 通过WCF Extension实现Context信息的传递
- 理性的相亲方法!精品课:《决策树》
- Asp.Net无刷新分页( jquery.pagination.js)
- 为什么网站需要用CDN来加速?
- Jmeter常用获取数据的几种方式
- [Silverlight 4 RC]RichTextBox概览
- WCF后续之旅(4):WCF Extension Point 概览
- Asp.Net无刷新上传并裁剪头像
- 用泛型的IEqualityComparer<T>接口去重复项
- python与office(一)
- Asp.net 后台添加CSS、JS、Meta标签(帮助类)
- 分享一下cookies操作(增、删、改、查)小经验
- [Silverlight 4 RC]WebBrowserBrush概览
- 一个例子理解C#位移
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 元数据管理|Hive Hooks和Metastore监听器介绍
- Go 每日一库之 fyne
- 实时数仓|基于Flink1.11的SQL构建实时数仓探索实践
- 使用 fyne 编写一个计算器程序
- Go 每日一库之 negroni
- Go 每日一库之 cli
- Go 每日一库之 cron
- Go 每日一库之 mapstructure
- Go 每日一库之 jobrunner
- 在本地运行 fyne 官网
- Python-科学计算-pandas-12-df单列计算
- JDK15就要来了,你却还不知道JDK8的新特性!
- 八佰:用Python看知乎 vs 豆瓣的战斗
- 学习Python你必须了解的lenna小姐姐
- 10行python代码制作笑死人不偿命的倒放gif