首页 > 其他分享 >点分治 - 知识点梳理

点分治 - 知识点梳理

时间:2023-07-17 19:35:04浏览次数:37  
标签:知识点 子树 int 分治 节点 curm 梳理 size

分治是一种将大的问题拆分成更小的问题并分而治之的算法,有利于使一个大的问题迅速缩小。在线性区间上的分治一般从区间中点将区间一分为二(这个中点也叫做分治中心),这样分 \(\log n\) 次就能使区间大小缩小到 \(1\)。而在树上的分治一般以树的重心作为分治中心,这样分 \(\log n\) 次也能将范围缩小到单个节点,这就是点分治。作为一种分治算法,点分治的主要功能是使一个大的问题迅速缩小,这样就能将一些趋近于暴力的算法成功地在每个小问题上应用了。
比较简单的点分治题常关于解决树上点对问题,而更难的点分治题会涉及到各种树上问题,但这些问题大部分都和树的局部形态不相关。

例题

例题:Luogu P3806 【模板】点分治1
这就是点分治最简单的应用:树上点对问题。
首先考虑暴力的做法是 \(O(n^2\log n)\) 枚举每两个点的 LCA,然后再算距离。
还有一种做法就是以任意节点为根,建立一棵树,对于每个根节点,枚举以该节点为 LCA 的路径,即求出子节点的深度,看有没有两个加起来等于 \(k\)。这种算法的复杂度为 \(O(n^2)\)。
这种算法的本质是先求过根节点的链,再求不过根节点的链。所有不过根节点的链都只会经过其所在的唯一子树。换句话说,其实访问子树时只和子树的形态有关,与上面部分的形态无关。这样的方法其实是先求经过整棵树的答案,再求每棵子树内部的答案。这不就是分治吗?
这时我们思考这种方法的瓶颈:每一次询问子树时,我们都要从以固定根节点得出的子树的根作为分治中心,这样子节点的不均匀性可能导致分治的规模缩小地太慢。因为只和子树的形态有关,所以我们可以在子树上再重新选择一个根节点并计算,这样就可以保证规模缩小的速度了。
那究竟要选哪个点作为分治中心呢?要让规模缩小地最快,要让每次分治时分出来最大的子树尽量小,于是树的重心满足这个条件。事实上,选择树的重心作为分治中心复杂度为 \(O(n\log n)\)。
做法就出来了:每次分治时,遍历分治中心的所有子树,用一个桶存储子树内每个节点的深度,再对于每个节点通过桶查询与之深度和为 \(k\) 的点是否存在。完成后,删除当前节点,为每个子树找到分治中心,递归进行分治处理。
上代码!

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 10005, MAXM = 105, MAXK = 10000005;
struct edge {
	int to, val;
};
vector<edge> to[MAXN];
bool vis[MAXN];
int n, m, k[MAXM], size[MAXN], root, rootm, tot;
void getroot(int x, int fa = 0x3f3f3f3f) {
	int curm = 0;
	size[x] = 1;
	for (edge p: to[x]) {
		if (p.to == fa || vis[p.to]) continue;
		getroot(p.to, x);
		size[x] += size[p.to];
		curm = max(curm, size[p.to]);
	}
	curm = max(curm, tot - size[x] - 1);
	if (curm < rootm) {
		rootm = curm;
		root = x;
	}
}

int ans[MAXK], t[MAXK]; 
void dfs(int x, int fa = 0x3f3f3f3f, int pres = 0) {
//	printf("	DFS %d PRES = %d\n", x, pres);
	for (int i = 1;i <= m;i++){
		if (pres <= k[i]) ans[i] += t[k[i] - pres];
	}
	for (edge p: to[x]) {
		if (p.to == fa || vis[p.to]) continue;
		dfs(p.to, x, pres + p.val);
	}
}
void calc(int x, int fa = 0x3f3f3f3f, int pres = 0) {
//	printf("	CALC %d PRES = %d\n", x, pres);
	if (pres <= MAXK) t[pres] += 1;
	for (edge p: to[x]) {
		if (p.to == fa || vis[p.to]) continue;
		calc(p.to, x, pres + p.val);
	}
}
void clear(int x, int fa = 0x3f3f3f3f, int pres = 0) {
	if (pres <= MAXK) t[pres] = 0;
	for (edge p: to[x]) {
		if (p.to == fa || vis[p.to]) continue;
		clear(p.to, x, pres + p.val);
	}
}
void solve(int x) {
	t[0] = 1;
	vis[x] = true;
	for (edge p: to[x]){
		if (vis[p.to]) continue;
		dfs(p.to, 0x3f3f3f3f, p.val);
		calc(p.to, 0x3f3f3f3f, p.val);
	}
	clear(x);
	for (edge p: to[x]) {
		if (vis[p.to]) continue;
		root = 0;
		rootm = 0x3f3f3f3f;
		tot = size[x];
		getroot(p.to, 0x3f3f3f3f);
		solve(root);
	}
}
int main() {
	scanf("%d%d", &n, &m);
	int x, y, w;
	for (int i = 1; i <= n-1; i++) {
		scanf("%d%d%d", &x, &y, &w);
		edge new1, new2;
		new1.to = y;
		new1.val = w;
		to[x].push_back(new1);
		new2.to = x;
		new2.val = w;
		to[y].push_back(new2);
	}
	for (int i = 1;i <= m;i++){
		scanf("%d", &k[i]);
	}
	root = 0;
	rootm = 0x3f3f3f3f;
	tot = n;
	getroot(1);
	solve(root);
	for (int i = 1; i <= m; i++) {
		if (ans[i]) printf("AYE\n");
		else printf("NAY\n");
	}
	return 0;
}

一定要记住树上点对只是点分治最简单的应用。

标签:知识点,子树,int,分治,节点,curm,梳理,size
From: https://www.cnblogs.com/mindeveloped/p/shoot-the-point-down.html

相关文章

  • 分治法处理大整数相乘问题
    分治法解决大整数相乘问题1.题目描述大数乘法法运算跟一般的减法运算是不同的,在面对基本数据类型容量有限而导致无法存储特大数字的情况下,本文采用分治策略的方法来解决大数减运算问题。输入:两个代表整数的字符串a和b,规定a>=b,a,b>0。输出:返回表示结果整数的字符串。2.解决......
  • 线段树分治结构
    目录线段树分治结构基本知识:例题1: 模板(基础题)例题2: dp(背包)(会了就掌握题)线段树分治结构基本知识:应用点: 当有些东西一会出现,一会又不出现时,可以使用线段树分治方式: 维护某一个东西出现的时间段,并在线段树上打上标记,并dfs遇到标记后,就相当于加入了这个物品。当dfs到叶子节点后,就......
  • 拓扑排序算法相关的知识点总结
    拓扑排序算法相关的知识点总结拓扑排序算法是一种对有向无环图(DAG)进行排序的方法,它可以将图中的所有顶点排成一个线性序列,使得对于任意一对顶点u和v,如果存在一条从u到v的有向边,那么u在序列中必然出现在v之前。拓扑排序算法可以用来解决一些依赖关系的问题,例如课程安排、工程进度......
  • python知识点
    anoldcat 博客园首页新随笔联系订阅管理随笔-66  文章-61  评论-7  阅读- 14万Python知识点大全(转载) 转载自:https://github.com/kenwoodjw/python_interview_question大佬总结得很好,本来我也想总结一个的,直到我看到了这个。。。额,我......
  • 树分治
    点分治回想一下在序列上的二分治:每次选择序列中点,递归处理两个子序列,处理跨过两个序列的信息。前两步保证了复杂度,第三步往往是解决问题的关键,那么这个思路能不能搬到树上呢?答案是肯定的,树分治思路和序列分治类似,我们每次递归处理子树,统计子树间的答案..,初步建立起模型后还有一......
  • 平衡树 - 知识点梳理
    _本文中一行代码都没有,因为平衡树的代码变化很大,关键是理解思想,代码根本不重要,需要代码可以看我的提交记录_平衡树是一种优化过的BST。BST由于没有保证深度因此每次查询复杂度最坏为\(O(n)\),而平衡树通过在中序遍历不改变的情况下对树的结构做一些适当的调整,使每次查询时间复杂......
  • Thread 和 ThreadPool 简单梳理(C#)【并发编程系列】
    〇、前言对于Thread和ThreadPool已经是元老级别的类了。Thread是C#语言对线程对象的封装,它从.NET1.0版本就有了,然后ThreadPool是.NetFramework2.0版本中出现的,都是相当成熟的存在。当然,现在已经出现了Task和PLinq等更高效率的并发类,线程和线程池在实际开发......
  • flume知识点总结
    flume知识点总结1.flume作用:从各种各样的数据源采集数据(读数据,缓存数据,写数据)到各种各样的文件系统中,如kafka 2.flume的采集程序:agent(包括source组件,channel组件,sink组件) 3.flume基本配置:(dir)#定义三大组件的名称ag1.sources=source1ag1.sinks=sink1ag1.channels=c......
  • 分治FFT
    考虑一下递推式:\[f_{i}=\sum\limits_{j=1}^ig_jf_{i-j}\]如果要暴力计算的话复杂度是\(n^2\),考虑到后面的是卷积的形式,但是唯一的问题是\(f\)自己得出自己。考虑分治。设当前分治区间为\(l,r\),首先分治计算\(l,mid\)的所有\(f\)值,然后考虑\(l,mid\)给\(mid+1,r\)......
  • NOI 2023 考前知识点总复习
    NOI2023考前知识点总复习其实就是把熟悉或不熟悉的东西再过一遍,防止考场上出现会了做法却因为忘了算法而写不出来的问题。可能会一句话概括,也可能附上一点代码片段。如果不想复习知识点,只想要一点考前提示,可以直接翻到本文最底部。目录I.数据结构、树上问题II.数论III.......