各种分治总结

各种分治总结

前言

分治还是实用的,但教练把点分治和 cdq 放一起也是神秘了。

点分治

为表述方便,下面定义:

  • 根链=以根为起点的链
  • 什么(的)子树=以什么为根的子树

算法思想

这个板子不是很简单啊,提前说一下作者的变量名起的比较随意。

link 这个题要求是否存在距离为 \(k\) 的点对。

那么显然是有树上差分做法的,但慢如屎,不介绍。

考虑到可以将路径按经不经过根划分为两类,其中经过根的可以用两个根链拼成(等下会说细节),而不经过的可以递归处理

那为了递归时复杂度尽可能低,我们对于每个递归到的子树都把其重心\(^1\)当成根后处理即可。

  1. 重心定义为一颗树中的一颗节点使其最大儿子子树最小

详细流程

比如说这样一颗树:

我们注意到他十分的丑陋,因为他的左边无比强壮,但右边却相当虚弱,这导致他根的最大儿子子树\(^2\)大小为12但当我们把根换为3:

  1. 因为子树越大所需的递归次数越多,一个点被便利的次数越多,复杂度越大,所以这个值是影响复杂度的关键之处。

那么此时根的最大儿子子树大小就为6,显然是优了很多,这就是为什么每次都要把重心当成根的原因。

于是,我们需要一个找重心的 dfs:

void grt(int x,int f){//get root
	wet[x]=0;//weight的缩写,wet[i]表示i的最大儿子子树大小,为方便写代码,wet[0]=n
	siz[x]=1;//siz[i]表示i的子树大小
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==f||vis[v]) continue;
		grt(v,x);
		siz[x]+=siz[v];
		wet[x]=max(wet[x],siz[v]);
	}
    //all是目前递归的整棵树的大小
	wet[x]=max(wet[x],all-siz[x]);//若这个节点为根,那么最后一个儿子子树是除自己子树外的所有点
	if(wet[x]<wet[rt]) rt=x;//root的缩写,表示找到的重心
	return;
}

那么现在重心找完了,我们要怎么处理经过根的路径呢,刚刚思路说了可以用两个根链拼成,但可能会有问题:

对,这就是问题,但我们发现只要是终点是不同儿子子树的根链就不会有这个问题,而反之则绝对会有这个问题,于是我们就可以通过一下方法规避掉这种情况:

void gds(int x,int f){//get dis,用于预处理一个节点到根的距离
	st[++num]=dis[x];//st 是栈,等等会用
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].to;
		if(vis[v]||v==f) continue;
		dis[v]=dis[x]+e[i].w;//处理dis
		gds(v,x);
	}
	return;
}
void solve(int x){//用于处理以x为根的子树的子问题
	int cnt=0;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].to;
		if(vis[v]){
			continue;
		}
		num=0;//清空栈
		dis[v]=e[i].w;//初始化dis
		gds(v,x);
        //先更新答案再清空是为了防止两条链终点在一个儿子子树中
		for(int j=1;j<=num;j++){//用当前的儿子子树中的链更新答案
			for(int k=1;k<=m;k++){
				if(que[k]>=st[j]){//防止RE
					ans[k]|=tong[que[k]-st[j]];
				}
			}
		}
		for(int j=1;j<=num;j++){
			if(st[j]>10000000){//防止越界,询问只到这么大
				continue;
			}
			mt[++cnt]=st[j];//mt是另一个栈,叫这个名字是与st相对,用于清空
			tong[st[j]]=1;
		}
	}
	for(int i=1;i<=cnt;i++){//这里memset就n方了
		tong[mt[i]]=0;
	}
	return;
}

At last,我们需要一个用于递归的 dfs。

void gsz(int x,int f){//用于重新求siz
	siz[x]=1;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==f||vis[v]) continue;
		gsz(v,x);
		siz[x]+=siz[v];
	}
	return;
}
void dfs(int x){
	vis[x]=tong[0]=1;//把根删掉+长度为0的链存在
	solve(x);//求解当前子树的答案
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].to;
		if(vis[v]) continue;
		wet[0]=n;//这样当rt为0时任何点都能更新它
		rt=0;
		all=siz[v];//设置all
		grt(v,0);//找儿子子树的重心
        gsz(rt,0);//如果不重新找siz下一轮的all就是错的
		dfs(rt);//继续递归
	}
	return;
}

注意这个:gsz(rt,0);//如果不重新找siz下一轮的all就是错的,但其实不影响复杂度,属于一种奇技淫巧吧,具体可见,我重置是害怕有的题要用 \(siz\)\(all\) 然后被板子背刺。

哦有的人可能会想要一个完整的代码

来吧看几道例题:

[bzoj1468] Tree

题目大意

原题晰。

题解

相对于板子只改了一点啊,对于所有的根链按长度排序一下双指针扫即可。

那有人就要问了,你排完序后遇到这种情况:

你不炸了吗!

诶,我一容斥,不就赢了吗!然后就解决了。

[国家集训队] 聪聪可可

题目大意

求在一颗树上随机取两点,它们的最短路径长度是3倍数的概率。

题解

版子,把所有边都找到算出总可能数即可,注意取两次意味着它们可以是一个点,所以答案要 \(+n\)

[IOI 2011] Race

题目大意

求树上边数最少的长为 \(k\) 的路径。

题解

还是板子,注意到和我们最开始的板子只加了一个求最少边数,遂考虑把原本记录是否存在的桶改为记录最短边数,不存在就设为 \(inf\),然后加一个 \(dim_i\) 表示到 \(i\) 的边数即可。

[USACO13OPEN] Yin and Yang G

题目大意

原题晰。

题解

首先有典中典之把黑白转化为权值为 \(1\)\(-1\) 的点。

注意到:记 \(d_i\) 表示 \(i\) 到重的权值和,对于答案有贡献的点 \(x,y\) 有以下性质:

  1. \(d_x+d_y=0\)
  2. 有点 \(z\) 满足以下两者之一:①其为 \(x\) 祖先且 \(d_z=d_x\) ②其为 \(y\) 祖先且 \(d_z=d_y\)

于是把点按有无祖先的 \(d\) 值与自己相同分类后简单维护维护就行了。

[BJOI2017] 树的难题

题目大意

原题晰。

题解

\(c_i\) 表示从根到 \(i\) 路径的第一条边的颜色。

然后如果两个点 \(x,y\) 满足 \(c_x=c_y\),那么更新贡献时就要减 \(c_x\) 的权值,反之不用。

于是把相同的放一块,不同的放一块算贡献,用单调队列维护,实际实现有点💩。

点分树

就是按点分治的 \(vis\) 标记顺序重构树,它有以下性质:

  1. 深度为 \(\log_n\)
  2. 两节点在点分树上的 \(lca\) 必在它原树的路径上

[BZOJ3730] 震波

题目大意

原题晰。

题解

板子,对每个节点建2棵线段树,一颗维护子树到自己,一颗维护子树到父亲,下标为距离即可。

[ZJOI2015] 幻想乡战略游戏

题目大意

给定一颗树有边权点权,点权带修,找一个点使得它到所有树上的点 \(u\) 的距离乘 \(u\) 的点权之和最小,求这个最小值。

题解

注意到可以让重心在修改后动态移动,在点分树上动即可。

[HNOI2015] 开店

题目大意

给定一颗树有边权点权,求某个点到所有点权在 \([L,R]\) 中的点的距离之和,强制在线。

题解

考虑如何求某个点到所有点的距离之和,发现和上题一模一样。

然后对颜色再开一维,vector 上二分即可。

cdq分治

算法简介

是思想不是算法,思想是分治求解然后每次用左区间更新右区间。

[bzoj3262] 陌上花开

题目大意

求三维偏序。

题解

先按 \(x\) 排序,然后分治每次考虑左区间对右区间的贡献,注意到这就是二维偏序了,对左右区间分别按 \(y\) 排序,然后用树状数组维护 \(z\) 维的贡献即可。

[BalkanOI2007] Mokia 摩基亚

题目大意

单点修改,矩阵查询

题解

把矩阵拆成4个点,然后时间和 \(x,y\) 构成三维偏序。

[bzoj2716] 天使玩偶

题目大意

单点修改,查询曼哈顿距离最近的点的曼哈顿距离。

题解

时间和 \(x,y\) 构成三维偏序。

[CQOI2011] 动态逆序对

题目大意

动态逆序对。

题解

时间和 \(x,y\) 构成三维偏序。

[HEOI2016/TJOI2016] 序列

题目大意

原题晰。

题解

考虑要有贡献首先是左右一维,然后左边的那一个的最小要比右边的本身大,左边的本身要比右边的最大大,然后三维偏序。

posted @ 2025-05-03 17:54  LEWISAK  阅读(29)  评论(0)    收藏  举报