「学习笔记」略谈点分治

这篇具有很好参考价值的文章主要介绍了「学习笔记」略谈点分治。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

引入

给定一棵 \(n\) 个点树和一个整数 \(k\),求树上两点间的距离小于等于 \(k\) 的点对有多少。

对于这个题,如果我们进行 \(O_{n^3}\) 搜索,那只要 \(n\) 一大,铁定超时。

所以,我们要用一个更优秀的解法,这就是我们的点分治。

变量

typedef pair<int, int> pii;

const int N = 4e4 + 10;

int n, k, rt, ans, sum;
int siz[N], maxp[N], dis[N], ok[N];
bool vis[N];
vector<pii> son[N];

n: 点数;

k: 限定距离;

rt: 根节点;

sum: 总结点数(找重心要用到);

siz: 子树大小;

maxp: 最大的子树的大小;

dis: 每个节点到根节点的距离;

ok: 栈;

vis: 标记;

son: 存图。

过程

1. 找重心

为什么是找重心?

其所有的子树中最大的子树节点数最少,在所有点中,重心是最优的选择。

找到重心后,以重心为根开始操作。

void get_root(int u, int fat) {
	siz[u] = 1;
	maxp[u] = 0;
	for (pii it : son[u]) {
		int v = it.first;
		if (v == fat || vis[v])	continue;
		get_root(v, u);
		siz[u] += siz[v];
		maxp[u] = max(maxp[u], siz[v]);
	}
	maxp[u] = max(maxp[u], sum - siz[u]);
	if (maxp[u] < maxp[rt])	rt = u;
}

这里并不是很难。

2. 处理答案

对于每个根节点,我们进行搜索,会得到每个节点到根节点的距离。

我们现在要求出经过根节点的距离小于等于 \(k\) 的点对个数。

我们将所有点的距离从小到大排一个序,设置左右两个指针,如果左指针和右指针所指向的节点到根节点的距离小于等于 \(k\),则两个指针之间所有的节点到左指针所指向的节点的距离都小于等于 \(k\),与此同时 l ++,如果左右指针所指向的节点的距离之和大于 \(k\),那么右指针就要左移,即 -- r

然后我们对每个节点都这样搜一遍,将答案加出来,就可以轻松加愉快的切掉这个问题了
吗?
考虑一下,如果是下面这种情况

「学习笔记」略谈点分治

假设 \(k = 5\),那么以 \(1\) 为根节点时,\(4\)\(5\) 很显然是符合的 \(\left(5 \rightarrow 3 \rightarrow 1 \rightarrow 3 \rightarrow 4 \right)\),我们将它加入答案。

然后,当我们又以 \(3\) 为根节点时,\(4\)\(5\) 这个点对我们就又统计了一次。

有什么问题?重复啦!

原因也很简单,因为 \(4\)\(5\) 在同一个子树内,因此只要它们在这个大的树内符合要求,那么它们在它们的小子树内也一定符合要求,那么就一定会有重复,因此,利用容斥的原理,我们先求出总的答案,然后再减去重复的部分。

如何检验重复的部分呢?

我们发现它们共同经过了一条边 \(1 - 3\),所以我们再次搜索,这次直接初始化 dis[3] = 1,然后其他的依旧按照操作,最后如果他们的距离之和小于等于 \(k\),则这就是重复的部分,统计一下,最后减去即可,即 \(dis_4 + dis_5 \le k\) 的话,就是重复的。

减去之后,就在子树里找重心,设置新的根节点,开始新的答案统计,与此同时,我们要将原来的根节点打上标记,防止搜索范围离开了这个子树。

(或许这就是点“分治”的所在,搜完一个重心后,相当于把这个重心删除,然后就将一颗树分成多个互相之间没有联系的小子树,各自进行搜索)

int calc(int u, int val) {
	ok[0] = 0;
	dis[u] = val;
	dfs(u, 0);
	sort(ok + 1, ok + ok[0] + 1);
	int cnt = 0, l = 1, r = ok[0];
	while (l < r) {
		if (ok[l] + ok[r] <= k) {
			cnt += (r - l ++);
		}
		else {
			r --;
		}
	}
	return cnt;
}

void work(int u) {
	ans += calc(u, 0);
	vis[u] = 1;
	for (pii it : son[u]) {
		int v = it.first, w = it.second;
		if (vis[v])	continue;
		ans -= calc(v, w);
		maxp[rt = 0] = sum = siz[v];
		get_root(v, 0);
		work(rt);
	}
}

关于 sum = siz[v];,当我们再次找重心时,是要在这个子树中找重心,不能超出这个子树,因此要将总个数也设为 siz[v]

3. 处理到根节点的距离

这个,应该就没什么好说的了。

void dfs(int u, int fat) {
	ok[++ ok[0]] = dis[u];
	siz[u] = 1;
	for (pii it : son[u]) {
		int v = it.first, w = it.second;
		if (v == fat || vis[v])	continue;
		dis[v] = dis[u] + w;
		dfs(v, u);
		siz[u] += siz[v];
	}
}

看到这,你已经看完了点分治的核心步骤,让我们把代码整合一下,去切掉这道模板题 Tree

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;

const int N = 4e4 + 10;

int n, k, rt, ans, sum;
int siz[N], maxp[N], dis[N], ok[N];
bool vis[N];
vector<pii> son[N];

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

void get_root(int u, int fat) {
	siz[u] = 1;
	maxp[u] = 0;
	for (pii it : son[u]) {
		int v = it.first;
		if (v == fat || vis[v])	continue;
		get_root(v, u);
		siz[u] += siz[v];
		maxp[u] = max(maxp[u], siz[v]);
	}
	maxp[u] = max(maxp[u], sum - siz[u]);
	if (maxp[u] < maxp[rt])	rt = u;
}

void dfs(int u, int fat) {
	ok[++ ok[0]] = dis[u];
	siz[u] = 1;
	for (pii it : son[u]) {
		int v = it.first, w = it.second;
		if (v == fat || vis[v])	continue;
		dis[v] = dis[u] + w;
		dfs(v, u);
		siz[u] += siz[v];
	}
}

int calc(int u, int val) {
	ok[0] = 0;
	dis[u] = val;
	dfs(u, 0);
	sort(ok + 1, ok + ok[0] + 1);
	int cnt = 0, l = 1, r = ok[0];
	while (l < r) {
		if (ok[l] + ok[r] <= k) {
			cnt += (r - l ++);
		}
		else {
			r --;
		}
	}
	return cnt;
}

void work(int u) {
	ans += calc(u, 0);
	vis[u] = 1;
	for (pii it : son[u]) {
		int v = it.first, w = it.second;
		if (vis[v])	continue;
		ans -= calc(v, w);
		maxp[rt = 0] = sum = siz[v];
		get_root(v, 0);
		work(rt);
	}
}

int main() {
	n = read();
	for (int i = 1, u, v, w; i < n; ++ i) {
		u = read(), v = read(), w = read();
		son[u].push_back({v, w});
		son[v].push_back({u, w});
	}
	k = read();
	maxp[rt = 0] = sum = n;
	get_root(1, 0);
	work(rt);
	printf("%d\n", ans);
	return 0;
}

其他题目

模板题(大雾)文章来源地址https://www.toymoban.com/news/detail-457823.html

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, ll> pil;

const int N = 1e4 + 5;

int n, m, sum, rt;
int q[N], siz[N], maxs[N], can[N], dis[N];
int tp[N];
bool ok[N], vis[N];
vector<pil> son[N];

bool cmp(int x, int y) {
	return dis[x] < dis[y];
}

void get_root(int u, int fat, int tot) {
	siz[u] = 1;
	maxs[u] = 0;
	for (auto [v, w] : son[u]) {
		if (v == fat || vis[v])	continue;
		get_root(v, u, tot);
		siz[u] += siz[v];
		maxs[u] = max(siz[v], maxs[u]);
	}
	maxs[u] = max(maxs[u], tot - siz[u]);
	if (!rt || maxs[u] < maxs[rt]) {
		rt = u;
	}
}

void dfs(int u, int fat, int d, int from) {
	can[++ can[0]] = u;
	dis[u] = d;
	tp[u] = from;
	for (auto [v, w] : son[u]) {
		if (v == fat || vis[v])	continue;
		dfs(v, u, d + w, from);
	}
}

void calc(int u) {
	can[0] = 0;
	can[++ can[0]] = u;
	dis[u] = 0;
	tp[u] = u;
	for (auto [v, w] : son[u]) {
		if (vis[v])	continue;
		dfs(v, u, w, v);
	}
	sort(can + 1, can + can[0] + 1, cmp);
	for (int i = 1; i <= m; ++ i) {
		int l = 1, r = can[0];
		if (ok[i])	continue;
		while (l < r) {
			if (dis[can[l]] + dis[can[r]] > q[i]) {
				r --;
			}
			else if (dis[can[l]] + dis[can[r]] < q[i]) {
				++ l;
			}
			else if (tp[can[l]] == tp[can[r]]) {
				if (dis[can[r]] == dis[can[r - 1]]) {
					-- r;
				}
				else ++ l;
			}
			else {
				ok[i] = true;
				break;
			}
		}
	}
}

void work(int u) {
	vis[u] = true;
	calc(u);
	for (auto [v, w] : son[u]) {
		if (vis[v])	continue;
		rt = 0;
		get_root(v, 0, siz[v]);
		work(rt);
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for (int i = 1, x, y, z; i < n; ++ i) {
		cin >> x >> y >> z;
		son[x].push_back({y, z});
		son[y].push_back({x, z});
	}
	for (int i = 1; i <= m; ++ i) {
		cin >> q[i];
		if (!q[i])	ok[i] = 1;
	}
	maxs[0] = n;
	get_root(1, 0, n);
	work(rt);
	for (int i = 1; i <= m; ++ i) {
		if (ok[i]) {
			cout << "AYE" << '\n';
		}
		else {
			cout << "NAY" << '\n';
		}
	}
	return 0;
}

到了这里,关于「学习笔记」略谈点分治的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 算法:分治思想处理快排递归以及快速选择/最小K个数问题

    分治的原理就是分而治之,从原理上讲,就是把一个复杂的问题划分成子问题,再将子问题继续划分,直到可以解决 基于分治的原理进行快速排序,区别于传统的快速排序,这里对快速排序进行改良,成为更优先的三路划分算法,可以处理一些极端场景,使快速排序的适用性

    2024年02月10日
    浏览(55)
  • 【Android学习笔记】图形与图像处理(动态处理)

    逐帧动画 AnimationDrawable与逐帧动画。在元素中定义子元素,表示动画的全部帧,并制定持续时间即可。 animation-list xmlns:android=\\\"“android:onshot=true/false item android:drawable=”@package_name:drawable/resource_name\\\"android:duration=“integer”/ /animation-list 补间动画 android使用Animation代表抽象的动画类

    2024年02月12日
    浏览(46)
  • 图像处理学习笔记(一)

    一、基础知识 1、彩色图像 (1)RGB RGB色彩模式是工业界的一种颜色标准,通过对红R、绿G、蓝B三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。 图像中每个像素都分成R、

    2024年02月16日
    浏览(49)
  • 图像处理学习笔记

    图像处理的流程:获取图像-分割区域-特征提取。 嵌入式工业读码器 :包括DM码、QR码、vericode码 Blob分析与形态学 1.Blob区域是Blobs这一数据类型在halcon中的一种贴切的表达形式。 采集图像-区域分割,最后通过特征(如圆度、面积、矩形度等)筛选,这一过程被称为Blob(bin

    2024年02月14日
    浏览(48)
  • React学习笔记09- 事件处理

    React采用on+事件名的方式来绑定一个事件,注意,这里和原生的事件是有区别的,原生的事件全是小写 onclick , React里的事件是驼峰 onClick ,React的事件并不是原生事件,而是合成事件。   事件回调的几种写法 这种写法需要用bind处理回调函数不然获取不到this 这种写法不需要用

    2024年02月08日
    浏览(44)
  • 自然语言处理学习笔记(一)————概论

    目录 1.自然语言处理概念 2.自然语言与编程语言的比较 (1)词汇量: (2)结构化: (3)歧义性: (4)容错性: (5)易变性: (6)简略性: 3.自然语言处理的层次 (1)层次图  (2)自然语言处理系统输入源  (3)词法分析 (4)信息抽取 (5)文本分类与文本聚类 (

    2024年02月14日
    浏览(40)
  • 【图像处理】模板匹配的学习笔记

    cv.TM_CCOEFF cv.TM_CCOEFF_NORMED cv.TM_CCORR cv.TM_CCORR_NORMED cv.TM_SQDIFF cv.TM_SQDIFF_NORMED Note: cv2.TM_CCOEFF_NORMED :相较于其它方法,通常被认为具有较好的鲁棒性

    2024年02月10日
    浏览(34)
  • 自然语言处理学习笔记(六)————字典树

    目录 1.字典树 (1)为什么引入字典树 (2)字典树定义 (3)字典树的节点实现 (4)字典树的增删改查 DFA(确定有穷自动机) (5)优化 1.字典树 (1)为什么引入字典树         匹配算法的瓶颈之一在于 如何判断集合(词典)中是否含有字符串 。如果用有序集合TreeMap)的

    2024年02月13日
    浏览(49)
  • 自然语言处理学习笔记(四)————词典分词

    目录 1.中文分词 2.词典分词 (1)词的定义 (2)词典性质——齐夫定律  (3)词典 (4)加载词典  (5)hanlp词典路径 1.中文分词 中文分词 :指的是将一段文本拆分为一系列单词的过程,这些单词顺序拼接后等于原文本。 中文分词算法大致分为 基于词典规则 与 基于机器学

    2024年02月14日
    浏览(105)
  • 《Flink学习笔记》——第七章 处理函数

    为了让代码有更强大的表现力和易用性,Flink 本身提供了多层 API 在更底层,我们可以不定义任何具体的算子(比如 map,filter,或者 window),而只是提炼出一个统一的“处理”(process)操作——它是所有转换算子的一个概括性的表达,可以自定义处理逻辑,所以这一层接口

    2024年02月10日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包