首页 > 其他分享 >点分治

点分治

时间:2024-04-21 10:11:26浏览次数:19  
标签:head int siz 分治 Maxn 节点

1 概念

点分治是树分治的一种,主要用于处理树上路径问题。

注意这颗树不需要有根,即这是一颗无根树。

下面以例题分析点分治的基本思想。

2 基本思想

首先你需要会的前置知识:树的重心

我们来看这样一道例题:【模板】点分治

给出一颗无根树,有边权,询问树上是否存在距离为 \(k\) 的点。

首先会有显然的树上差分做法,不过太劣。

考虑这样一件事:对于树上的所有路径,可以分成两部分:

  1. 经过当前根节点的。
  2. 不经过当前根节点的。

对于第二种情况,他们一定在根节点的某个子树中。

我们发现,只要我们求出第一种情况对应的方案数,那么第二种情况就可以递归求解。

于是我们现在就有了点分治的初步思路:将路径分为经过与不经过根节点,对于后者继续递归分开求解。

但是如果直接随机取根节点求解,说不定会被卡成 \(O(n)\)(一条链)。此时就要提到上面强调的一个东西:由于原树是无根树,所有选择根节点的权利在于我们手里。我们只需要让他深度平衡,就可以保证复杂度为 \(O(\log n)\)。

那么如何让根节点平衡呢?这就要用到前置知识了:对于每一颗子树,让他的重心成为当前根节点就可以保证平衡。

所以我们总结一下点分治的基本步骤:

  1. 找出当前子树的重心。
  2. 根据题意对于当前子树的答案进行统计。
  3. 分治各个子树,重复步骤 \(1\)。

这就是点分治的基本思想了。

接下来就是代码了。

3 代码

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int Maxn = 2e5 + 5;
const int Maxm = 1e7 + 5;

int n, m;
int head[Maxn], edgenum;
struct node {
	int nxt, to, w;
}edge[Maxn];

void add(int from, int to, int w) {
	edge[++edgenum] = {head[from], to, w};
	head[from] = edgenum;
}

int q[Maxn];
bool ok[Maxm];

struct pdac {
	bool del[Maxn];
	int siz[Maxn], cen, sum;
	void getcen(int x, int fa) {//求重心
		siz[x] = 1;
		int s = 0;
		for(int i = head[x]; i; i = edge[i].nxt) {
			int to = edge[i].to;
			if(del[to] || to == fa) continue;
			getcen(to, x);
			siz[x] += siz[to];
			s = max(s, siz[to]);
		}
		s = max(s, sum - siz[x]);
		if(s <= sum / 2) cen = x;
	}
	int dis[Maxn], cnt, d[Maxn];
	void getdis(int x, int fa) {//求当前子树各个节点到根节点的距离
		dis[++cnt] = d[x];
		for(int i = head[x]; i; i = edge[i].nxt) {
			int to = edge[i].to;
			if(del[to] || to == fa) continue;
			d[to] = d[x] + edge[i].w;
			getdis(to, x);
		}
	}
	bool vis[Maxm];
	int p[Maxn], tot;
	void dfs(int x) {//点分治
		del[x] = 1;
		vis[0] = 1;
		tot = 0;
		for(int i = head[x]; i; i = edge[i].nxt) {
			int to = edge[i].to;
			if(del[to]) continue;
			cnt = 0, d[to] = edge[i].w;
			getdis(to, x);
			for(int j = 1; j <= cnt; j++) {
				for(int k = 1; k <= m; k++) {
					if(q[k] >= dis[j]) {
						ok[k] |= vis[q[k] - dis[j]];//能否拆分 q[k]
					}
				}
			}
			for(int j = 1; j <= cnt; j++) {
				if(dis[j] >= Maxm) continue;//注意距离可能大于 k 的最大值,此时我们不需要存储,否则会 RE
				p[++tot] = dis[j];
				vis[dis[j]] = 1;
			}
		}
		for(int i = 1; i <= tot; i++) {//不要用 memset,会 TLE
			vis[p[i]] = 0;
		}
        //上:处理当前点信息
        //下:分治求解
		for(int i = head[x]; i; i = edge[i].nxt) {
			int to = edge[i].to;
			if(del[to]) continue;
			sum = siz[to];
			getcen(to, 0);
			getcen(cen, 0);//跑两边,求出以重心为根各个子树的大小
			dfs(cen);
		}
	}
	void solve() {
		sum = n;
		getcen(1, 0);
		getcen(cen, 0);//同理,跑两边
		dfs(cen);
		for(int i = 1; i <= m; i++) {
			if(ok[i]) {
				cout << "AYE\n";
			}
			else {
				cout << "NAY\n";
			}
		}
	}
}T;

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 1; i < n; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		add(u, v, w), add(v, u, w);
	}
	for(int i = 1; i <= m; i++) {
		cin >> q[i];
	}
	T.solve();
	return 0;
}

标签:head,int,siz,分治,Maxn,节点
From: https://www.cnblogs.com/dzbblog/p/18148631

相关文章

  • 点分树(动态点分治)学习笔记
    1.定义在点分治的基础上加以变化,构造一颗支持快速修改的重构树,称之为点分树2.算法2.1.思路点分治的核心在于通过树的重心来划分联通块,减少合并层数,从而降低时间复杂度所以,我们可以按分治递归的顺序提出一颗树,易知树高至多为logn具体的说,对于每一个找到的重心,将上一次分治......
  • 点分治学习笔记
    前置知识:树的重心对于一颗无根树上的每一个点,计算其所有子树中最大的子节点数,这个值最小的点就是树的重心1.定义点分治,又叫树分治,点分治是一种在树上进行路径静态统计的算法,所谓树上的静态统计,并不是像树剖一样维护路径最值,路径之和一类的统计,点分治的本质其实是将一棵树拆分......
  • #莫队二次离线,根号分治#洛谷 5398 [Ynoi2018] GOSICK
    题目\(m\)组询问求\(\sum_{l\leqi,j\leqr}[a_i\bmoda_j==0],n,m,a_i\leq5\times10^5\)分析设\(f(l,r,x)\)表示\(i或j\in[1,x],i或j\in[l,r]\)时的答案,\(g_x\)表示\([1,x]\)的答案,根号的做法可以通过三秒由于涉及区间内的求值,需要在莫队的基础上二次离线,那......
  • [DS 小计] 线段树分治
    很简单的一个小trick。就看能不能想出来。思考我们学过CDQ分治。CDQ分治的思想是把时间切割,询问截断点后面的问题,预处理截断点前面的贡献。这是把问题和修改放在一起的。那么,假设修改很难撤销怎么办?这时候就可以用线段树分治做到只增不删。有点类似回滚莫队。算法直......
  • 分治思想排序(快速排序、归并排序)
    分治:分而治之,即把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并优点:降低时间复杂度:分治法可以将大问题分解为小问题,通过解决小问题来有效解决原问题,从而降低算法的时间复杂......
  • 算法分析与设计——实验1: 递归与分治
    实验一 递归与分治一、实验目的        1、理解分治算法的概念和基本要素;        2、理解递归的概念;        3、掌握设计有效算法的分治策略。二、实验内容和要求实验要求:通过上机实验进行算法实现,保存和打印出程序的运行结果,并结合程序进行......
  • 点分治
    最近学了点分治,觉得挺厉害的,准备写一下加深印象。树上的东西都很抽象,所以自然要用抽象的东西来做,就比如点分治,点分治的思路是在当前的子树中找重心,然后用重心做事情,为什么用重心,因为重心可以使得复杂度降到log级别,然后再在这个子树里找你要的东西就行了,因为是每次都会用子树做,所......
  • CF938G-动态连通图最短xor路(线段树分治、并查集、线性基)
    link:https://codeforces.com/contest/938/problem/G[!Description]有一张连通的无向图简单图,三种操作:1、加边\((x,y,d)\)2、删边\((x,y)\)3、询问\(x\toy\)的路径中异或最小的路径(不一定是简单路径)保证每次操作后图是连通的\(1\leqn,m,q\leq2\times10^5\).这......
  • 【博客】CDQ分治
    CDQ分治前言CDQ分治是一种分治CDQ分治是一种特殊的分治思想可以用于跟点对有关的问题还有其他用处......(目前不会)算法流程一般来说,cdq分治是通过如下结构进行分治一共分为三步:找到当前区间\([l,r]\)中点\(mid\)递归处理左右区间\([l,mid]\),\([mid+1,r]\)计算左区间......
  • 介绍分治算法
    分治算法是一种将问题划分成多个更小的子问题,并且分别解决这些子问题的策略。它通常包含三个步骤:分解(Divide):将原始问题划分成若干个更小的子问题。这个步骤可以使用递归实现,每次递归处理的子问题规模都要比原始问题小。解决(Conquer):递归地解决各个子问题,如果问题规模足够小,......