首页 > 其他分享 >点分治

点分治

时间:2024-08-13 21:37:44浏览次数:13  
标签:rt 路径 log int 复杂度 分治

参考博客

概述

点分治适合处理大规模的树上路径信息问题。——摘自 OI Wiki

时间复杂度 \(O(n\log n)\) (每次 \(calc\) 时间复杂度为 \(O(size_{root})\))。

对于树上的所有路径及一个假定的根 \(rt\) ,有两种路径:

  1. 经过 \(rt\) 的;
  2. 不经过 \(rt\) 的。

第一种路径显然分两部分(可以为空),分别位于不同的子树中。
显然第二种路径对于某个节点 \(u\) ,属于第一种路径。所以分治解决即可。

点分治时间复杂度为 \(O(nT)\) ,其中 \(T\) 为树的深度。(因为每次分治要一共处理整棵树,而最多要进行 \(T\) 次分治)。当树是一条链且每次选择链头分治时,时间复杂度就是 \(O(n^2)\) 了。

因此,我们每次寻找子树的重心作为 \(rt\) 进行分治(重心满足最大子树 \(size\) 最小)。时间复杂度 \(O(nlogn)\) 。

该算法大致流程如下:

  1. \(rt\gets\) 树的重心。
  2. 计算经过 \(rt\) 的路径贡献。
  3. 对 \(rt\) 的每棵子树分别找重心,重复流程。

大致 Code(未过编)

#include<bits/stdc++.h>
#define ll long long
#define pf printf
#define sf scanf
using namespace std;
const int N=1e5+7;
int n,m;
int u,v,w;
int q;
int head[N],cnt;
int siz[N],mxsiz[N];//siz 即子树大小,mxsiz 以 u 为根最大子树 size 
int rt;
bool de[N]; //点分治专属,是否删除 
struct edge{
	int to,val,ne;
}e[N<<1];
void add(int u,int v,int w){
	e[++cnt].val=w,e[cnt].to=v,e[cnt].ne=head[u];
	head[u]=cnt;
}
void getroot(int u,int fa){// 找根 
	siz[u]=1;// 初始化 size 
	mxsiz[u]=0;// 初始化 max_size 
	for(int i=head[u];i;i=e[i].ne){
		int v=e[i].to,w=e[i].val;
		if(de[v]||v==fa) continue;
		getroot(v,u);
		siz[u]+=siz[v];
		mxsiz[u]=max(mxsiz[u],siz[v]);
	}
	mxsiz[u]=max(mxsiz[u],n-mxsiz[u]);//以 u 为根的子树包括上面那一堆 
	if(mxsiz[u]<mxsiz[rt]) rt=u;
}
void getans(int u,int fa,int ans,int from){// from 表示结点 u 来自哪棵子树 
	// 存储 ans 
	for(int i=head[u];i;i=e[i].ne){
		int v=e[i].to,w=e[i].val;
		if(v==fa||de[v]) continue;
		getans(v,u,/*ans的一些操作*/,from);
	}
}
void calc(int u){
	for(int i=head[u];i;i=e[i].ne){//遍历每个子树 
		int v=e[i].to,w=e[i].val;
		if(de[v]) continue;
		getans(v,u,w,v);
	}
	// 对存储的 ans 进行一些操作 
}
void solve(int u){ // 点分治 
	de[u]=1;
	calc(u);// 计算经过 u 的路径的贡献 
	for(int i=head[u];i;i=e[i].ne){
		int v=e[i].to,w=e[i].val;
		if(de[v]) continue;
		rt=0;// 初始化 
		n=siz[v];//当前要找重心的子树的总大小 
		getroot(v,u);
		solve(rt);
	}
}
int main(){
	sf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		sf("%d%d%d",&u,&v,&w);
		add(u,v,w),add(v,u,w);
	}
	sf("%d",&q);
	mxsiz[0]=n;//初始化 
	getroot(1,0);
	solve(rt);
	pf("%d\n",ans);
}

经验

P3806 模板

暴力思路

暴力枚举两个点,然后倍增求LCA求距离。时间复杂度 \(O(n^2logn)\) 。

点分治做法

参考该题解

暴力做法重复算了很多次距离。

以重心为根,计算每个点到根的距离,并排序。枚举每个点, \(logn\) 求出符合的第二个点的数量(代码中双指针 \(O(n)\) )。

这样就求出了经过根的所有答案。

对于不经过根的答案,拆分子树,找到子树的重心,递归下去。

因为每次找的是重心,所以一共要递归 \(logn\) 次,每次算距离共要遍历整棵树,即 \(n\) 个点,排序共 \(nlogn\) ,找第二个点双指针共 \(n\) ,答案处理共 \(nm\) ,因此算法总复杂度为 \(nlog^2n+nlogn+nmlogn=O(nlog^2n+nmlogn)\) 。

P4178 Tree

给定一棵 \(n\) 个节点的树,每条边有边权,求出树上两点距离小于等于 \(k\) 的点对数量。

做法时间复杂度 \(O(nlog^2n)\) 。

一眼点分治,这里解释 \(calc\) (计算经过结点 \(u\) 的路径贡献)部分。

将 \(u\) 子树下的所有点记录深度并排序,双指针即可,时间复杂度 \(nlogn\) 。

需要减去路径起点和终点经过 \(u\) 的同一棵子树的情况,只需要遍历 \(u\) 的儿子,分别减去 \(calc(v)\) 即可。

P4149 Race

给定一棵 \(n\) 个节点的树,每条边有边权,求出树上两点距离等于 \(k\) 的路径最小边数。

树上路径问题,用点分治

时间复杂度 \(O(nlogn)\) 。

计算每个结点的贡献:\(clac\) 中用桶记录每个深度的最小边数即可,然后清空桶(不可 memset)。

其他和模板一样。

求第 k 长路径

给定一棵树,求树上第 k 长路径的长度。\(n,m\le 10^5\)。

考虑二分答案,二分长度 \(len\) 然后点分治求长度 \(\le len\) 的路径数量。时间复杂度 \(O(n\log^2n\log V)\)。

优化:发现每次点分治的过程都是一样的,没必要做 \(\log V\) 次。考虑到每次求经过重心的路径的方法是先求 \(calc(rt)\) 然后减去所有儿子的答案(减去重复经过一个儿子的答案)。开一个结构体记录点分治的过程,vector d_u; 记录经过点 \(u\) 的所有 \(dis\)。设 \(u\) 的子树大小为 \(size\),显然 vector 的空间为 \(size\)。对于每个点,我们要记录它自己和它的儿子,所以一共要开 \(2n\) 个 vector。

然后每次二分 \(O(n\log n)\) 统计长度 \(\le mid\) 的边数即可。

总时间复杂度 \(O(n\log n \log V )\)。

标签:rt,路径,log,int,复杂度,分治
From: https://www.cnblogs.com/liyixin0514/p/18357756

相关文章

  • cdq分治
    我觉得呢,cdq的本质就是在归并排序消掉一维的影响来处理多维偏序问题。既然本质跟二分有关,那很容易猜到cdq处理k维偏序的时间复杂度为\(O(Nlog^{k-1}N)\)三维偏序问题:形如:$求满足条件a_i<a_j,b_i<b_j,c_i<c_j且\(j!=i\)的j个数比较常考的就是三维偏序,一般做法就是sort消掉一......
  • CDQ分治
    P3810【模板】三维偏序(陌上花开)CDQ模板题,考虑先按\(a\)排序,减掉一位,然后再\(CDQ\)分治一维,用树状数组维护最后一维还有本题特殊,去重操作不要忘记点击查看代码#include<bits/stdc++.h>#definespeed()ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);#definell......
  • CDQ分治
    CDQ分治的思想最早由IOI2008金牌得主陈丹琦在高中时整理并总结,它也因此得名。CDQ分治的思想常用于离线解决点对之间的问题,最经典的如三维偏序。解决这类问题的过程为:将序列$(l,r)$分为。递归处理序列$(l,mid)$和$(mid+1,r)$中的答案。设计算法处理......
  • cdq分治总结
    \(cdq\)分治是一种离线分治算法,可以将动态问题改变为静态问题,不适用于强制在线。其实现时通常将需要进行的操作存进一个结构体,然后对这些操作进行分治。打\(cdq\)分治时一个直观的感受就是很好想思路,但就是不知道怎么打。。。它一共有三个需要干的1找到范围中点\(mid\)......
  • 【CDQ分治】三元环
    三元环HDU-7439思路考虑\(3\)个点的有向图,要么成环,要么有一个点入度为\(2\),假设第个点的入度为\(d_i\),答案为\(C_n^3-\sum\limits_{i=1}^nC_{d_i}^2\)。根据题目关系,\(i\rightarrowj\)当且仅当\(i<j\and\f_i<f_j\and\g_i<g_j\),否则就是\(j\rightarrowi......
  • 【CDQ分治】【模板】三维偏序(陌上花开)
    P3810【模板】三维偏序(陌上花开)-洛谷|计算机科学教育新生态(luogu.com.cn)#include<bits/stdc++.h>usingnamespacestd;usingi64=longlong;template<typenameT>structBIT{#ifndeflowbit#definelowbit(x)(x&(-x));#endifintn;vector<T&......
  • 【CDQ分治】[P5094 [USACO04OPEN] MooFest G 加强版
    P5094[USACO04OPEN]MooFestG加强版-洛谷|计算机科学教育新生态(luogu.com.cn)#include<bits/stdc++.h>usingnamespacestd;usingi64=longlong;intmain(){ios::sync_with_stdio(false);cin.tie(nullptr);intn;cin>>n;vecto......
  • [算法] 一些分治
    普通分治其实没啥,每次只计算跨越分治中心的区间的贡献,剩下的递归到左右两边进行分治;时间复杂度:分治树高度为$\Theta(\logn)$,乘上其他操作的复杂度即可;例题一:现在有一个$n$阶排列$a$,计算:\[\sum^{n}_{i=1}\sum^{n}_{j=i}\min(a_i,a_{i+1},...,a_j)\]......
  • 20240806:点分治,虚树选做
    POJ-1741Tree题意:给定一棵树,求多少无序对\((u,v)\)满足\(\text{dist}(u,v)\lek\)。对于树上的路径进行分类:经过根;不经过根。第二类路径点可以通过递归子树求出。对于经过根的路径,可以一遍dfs求出每个点到根的距离\(\text{dis}(u)\)。问题转化为求\(\text{dis}(u)......
  • CDQ分治
    CDQ分治CDQ分治是一种思想而不是具体的算法,与动态规划类似。目前这个思想的拓展十分广泛,依原理与写法的不同,大致分为三类:解决和点对有关的问题。1D动态规划的优化与转移。通过CDQ分治,将一些动态问题转化为静态问题。解决和点对有关的问题这类问题多数类似于「给定一......