首页 > 其他分享 >点分治

点分治

时间:2023-10-12 22:01:24浏览次数:37  
标签:log int 复杂度 分治 len lens 节点

点分治

1.给定一个带边权的树,共有 \(m\) 个询问,询问距离为 \(k\) 的点对是否存在

做法1:暴力dfs

做法2:\(lca\) (时间复杂度\(O(n^2\log n)\))

做法3:点分治 (时间复杂度\(O(n\log n)\))

思路:

1.取一个节点 \(u\)

2.统计经过\(u\)的链

经过\(u\) 的链两端点必定在不同子树中

记录子节点到根节点的距离\(dist[i]\),依次统计所有子树,开一个\(lens\)数组记录已有路径长度。

当遍历一个子树时,查找是否存在\(lens[k-dist[i]]\)即可。

遍历完一个子树,开个栈存储该子树的\(dist\),再把该子树的路径全部加到\(lens\)中。

清空\(lens\)数组,为保证时间复杂度,开个栈记录添加的\(lens\),只清空栈中的元素即可

3.向下递归

如何选取节点:

若该树是一条链,如果每次都找其儿子递归,要递归\(n\)次,复杂度为\(O(n^2)\)

所以每次选取的节点\(u\)应该为该树的重心,只需递归\(\log n\)次,复杂度为\(O(n\log n)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=10010,M=20010,K=100000100,INF=0x3f3f3f3f;
int n,q,rt,minn=INF,root,maxx2;
int qs[N],ans[N];
int h[N],e[M],ne[M],w[M],idx;
int s[N],lens[K];
int stk[N],tt,stk2[N],tt2;
bool del[N];//该点是否已被遍历
inline int read()
{
	int x=0;
	char c=getchar();
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9'){
		x=(x<<3)+(x<<1)+(c^48);
		c=getchar();
	}
	return x;
}
void add(int a,int b,int c)
{
	e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dfs(int fa,int u,int zs)//找重心
{
	int maxx=0;
	s[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa||del[j])continue;
		dfs(u,j,zs);
		s[u]+=s[j];
		maxx=max(maxx,s[j]);
	}
	maxx=max(maxx,zs-s[u]);
	if(maxx<minn)minn=maxx,rt=u;
}
void dfs2(int fa,int u,int len)//统计以u为根的子树穿过u的路径
{
	if(len>maxx2)return;//长度大于询问的最大值,不用再向下统计
	stk[++tt]=len;
	for(int i=0;i<q;i++){
		if(qs[i]-len>=0&&qs[i]-len<K){
			if(lens[qs[i]-len]||len==qs[i])ans[i]=1;//存在另一子树长度为qs[i]-len,或其本身长度为qa[i]
		}
	}
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(del[j]||j==fa)continue;
		dfs2(u,j,len+w[i]);	
	}
}
void query(int u)
{
	del[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(del[j])continue;
		dfs2(u,j,w[i]);
		for(int l=1;l<=tt;l++){
			if(!lens[stk[l]])stk2[++tt2]=stk[l];
			lens[stk[l]]++;//将该子树的len加到lens中
		}
		tt=0;
	}
	for(int i=1;i<=tt2;i++)lens[stk2[i]]=0;//只清空栈中元素
	tt2=0;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(del[j])continue;
		minn=INF,rt=-1;
		int zs=s[j];
		dfs(u,j,zs);
		dfs(0,rt,zs);//维护size值(似乎不用)
		query(rt);//从每个子树重心开始递归
	}
}
int main()
{
	memset(h,-1,sizeof h);
	n=read(),q=read();
	for(int i=1;i<n;i++){
		int a,b,c;
		a=read(),b=read(),c=read();
		add(a,b,c),add(b,a,c);
	}
	dfs(0,1,n);
	root=rt;
	for(int i=0;i<q;i++)qs[i]=read(),maxx2=max(maxx2,qs[i]);//预处理出询问,统一查找
	query(root);
	for(int i=0;i<q;i++){
		if(ans[i])puts("AYE");
		else puts("NAY");
	}
	return 0;
} 

P4178 Tree

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

可用线段树维护,也有一种容斥+双指针的方法。

处理当前子树经过其根节点的所有长度,存到a数组中:

void getdis(int fa,int u,int len)
{
	dis[u]=len;
	a[++cnt]=dis[u];
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa||del[j])continue;
		getdis(u,j,len+w[i]);
	}
}

标签:log,int,复杂度,分治,len,lens,节点
From: https://www.cnblogs.com/lzaqwq/p/17760686.html

相关文章

  • 递归分治法在快速排序中的应用 java以界面的方式实现
    递归分治法在快速排序中的应用 分治法的基本思想§分治法的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同。 k个子问题分别求解。如果子问题的规模仍然不够小,则再划分为k个子问题,如此递归的进行下去,直到问题规模足够小,很容易求......
  • 72ed 2023/8/25 点分治学习笔记
    起因&介绍8月22号的T3是道黑,但思路却不算太难,就去打了这是第一次接触点分治,其实之前也有过一道点分治题,叫阴阳,但当时没去改,就一拖拖了半年才学点分治类似于树形DP,但在一些地方上处理有不同就比如在跑过根结点(1),进入处理它的子树时,会将其他的一部分视作没有(emmm大概这个意思,子树......
  • 基本技巧——根号分治 学习笔记
    基本技巧——根号分治学习笔记根号分治与其说是一个算法,更不如说是一种思想(trick)。定义根号分治,是一种对数据进行点分治的分治方式,它的作用是优化暴力算法;类似于分块,但应用范围比分块更广。具体来说,对于所进行的操作,按照某个点\(B\)划分,分为大于\(B\)及小于\(B\)两个部......
  • 线段树分治&可撤销并查集
    可撤销并查集按时间顺序用一个栈维护合并信息,撤销时从栈顶弹出合并信息,恢复原状态。并查集查找祖先时不能路径压缩,只能按秩合并。例题:[ABC302Ex]BallCollector容易想到将\(A_i\)和\(B_i\)之间连边。遍历整棵树,用可撤销并查集维护图。为了进一步求得答案,需要注意到该......
  • 分治算法
    基本思想:将序列分为\([l,mid]\)和\([mid+1,r]\),然后递归两边,同时再计算\([l,mid]\)与\([mid+1,r]\)影响所产生的答案(满足单调性的话一般使用走指针)。二维偏序首先将所有元素按\(x,y\)排序。然后递归两边,随后用两个指针\(i\)和\(j\),\(i\)从\(l\)到\(mid\),\(j\)......
  • <学习笔记>线段树分治
    一种离线处理方法可以处理“具体哪个修改对询问有影响”、可以贡献不独立、可以支持插入删除。例题对这道题来说,对修改开线段树,线段树上每个节点开一个\(vector\)来维护出现在这段区间的线段,加入一个线段的区间,直接在区间查询时对所包含的节点压入这条线段就可以。然后从根......
  • 【边分治】P4565 [CTSC2018] 暴力写挂
    初涉边分治。大致就是按照一条边的两端来分类出两套点,然后对这个进行分治,有点是只用考虑两类集合,考虑贡献很简单!!反正就是比点分强。但是如果直接分治是假的,会被菊花图薄纱,需要进行对树的三度化,这个是左儿子右兄弟,但是貌似很少人叫这个,比较不适,然后我们就可以做了。建出来的边分......
  • 简单分治快排问题解析(c++实现)
    这几天刷了需要使用分治快排思想去解决的几道比较好的题目,所以写下这篇博客用于复习和以后的复盘。什么是分治快排思想首先我们要知道什么是分治快排思想,这个思想其实就是在模拟实现qsort算法的时候使用的一个方法,在模拟实现qsort的时候,我们知道第一步是需要使用一个随意选择(三数取......
  • 【学习笔记】(26) cdq 分治 与 整体二分
    cdq分治基本思想我们要解决一系列问题,这些问题一般包含修改和查询操作,可以把这些问题排成一个序列,用一个区间[L,R]表示。分。递归处理左边区间\([L,M]\)和右边区间\([M+1,R]\)的问题。治。合并两个子问题,同时考虑到\([L,M]\)内的修改对\([M+1,R]\)内的查询产生的影......
  • 可持久化非确定状态AC自动分块维护线段平衡仙人掌优化最小费用最大流预处理混合图上莫
    P8946TheLostSymbol这种类型的dp的特点就是大部分转移形如\(f(i,j)\rightarrowf(i+1,j+1)\)之类的,并且当以上转移出现时原数组被清空,这就可以用一个deque来维护,然后对于全局赋值/全局加,需要对每个位置维护一个时间戳,并记录上一次赋值/加是什么时候,以便标记下传。(貌似......