首页 > 其他分享 >「学习笔记」略谈点分治

「学习笔记」略谈点分治

时间:2023-05-24 22:34:24浏览次数:43  
标签:rt ok 略谈 int siz 分治 笔记 节点 dis

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

引入

给定一棵 \(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
然后我们对每个节点都这样搜一遍,将答案加出来,就可以轻松加愉快的切掉这个问题了
吗?
考虑一下,如果是下面这种情况

image

假设 \(k = 5\),那么以 \(1\) 为根节点时,\(4\) 与 \(5\) 很显然是符合的,我们将它加入答案。
然后,当我们又以 \(3\) 为根节点时,\(4\) 和 \(5\) 这个点对我们就又统计了一次。
有什么问题?重复啦!
原因也很简单,因为 \(4\) 和 \(5\) 在同一个子树内,因此只要它们在这个大的树内符合要求,那么它们在它们的小子树内也一定符合要求,那么就一定会有重复,因此,利用容斥的原理,我们先求出总的答案,然后再减去重复的部分。
如何检验重复的部分呢?
我们发现它们共同经过了一条边 \(1 - 3\),所以我们再次搜索,这次直接初始化 dis[3] = 1,然后其他的依旧按照操作,最后如果他们的距离小于等于 \(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;
}

其他题目

模板题(大雾)

#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;
}

标签:rt,ok,略谈,int,siz,分治,笔记,节点,dis
From: https://www.cnblogs.com/yifan0305/p/17418898.html

相关文章

  • Java笔记(七):多线程
    Java默认有2个线程:main+GC并发:CPU单核,交替执行并行:CPU多核,多个线程可以同时执行(提高使用效率:线程池)Runtime.getRuntime().availableProcessors()//当前CPU可用核数多线程实现方式继承Thread类,重写run方法这样代码的写法简单,符合大家的习惯,但是直接继承Thread类有一......
  • *【学习笔记】(9) 分块
    分块思想引用一下oi-wiki的话:分块的基本思想是:通过对原数据的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。数列/序列分块引入#6280.数列分块入门4给定一长度为$n$的数列,有\(n\)次操作。操作分为两种:区间加,查询区间......
  • 【学习笔记】(15) Kruskal 重构树
    前置知识:kruskal求最小生成树,倍增。1.算法简介以下所有讨论基于最小生成树。在Kruskal求最小生成树的过程:将所有边按照边权排序,若当前边\((u,v)\)两端不连通,则往生成树边集\(E\)中加入\((u,v)\)并连接\(u,v\)。使用并查集维护连通性。如果能用某种结构描述每条边......
  • GitlabCI学习笔记之三:GitLabRunner pipeline语法之tags allow_faillure when retry ti
    1.tags用于从允许运行该项目的所有Runner列表中选择特定的Runner,在Runner注册期间,您可以指定Runner的标签。tags可让您使用指定了标签的runner来运行作业,此runner具有ruby和postgres标签。示例给定带有osx标签的OSXRunner和带有windows标签的WindowsRunner,以下作业将在......
  • 五月读书笔记二《人件集》
    继续阅读《人件集》后,体会到软件开发团队如果想要在项目中获得最大限度的成功,取决于团队中的成员能否形成技术性一致意见。但为什么这点如此重要呢?是不是团队成员只要在诸如目录表格的布局上达成一致,或者建立一个很好的错误汇报机制就行了呢?技术性一致意见指的并不是与同事打成......
  • 我的软考复习笔记【中级软件设计师】
    目录内聚与耦合内聚耦合统一过程(UP)软件体系结构风格软件能力成熟度模型(CMM)集成测试策略软件测试方法黑盒测试白盒测试需求UML分类协作图的边界类控制类实体类怎么区别null用例图的关系泛化(Inheritance)扩展(extend):包含(include):快速辨认类图的符号1.关联2.泛化3.聚合组件图设......
  • LaTeX 的学习笔记
    摘自我的洛谷博客该文章被打开的次数(包括洛谷平台):\(\LaTeX\)中所有命令都以\开头,后面可以跟一个花括号,代表参数。\documentclass{}指定了文章类型,有article(普通文章)、book(书)、beamer(幻灯片),如果要显示中文,有ctexart(普通文章),ctexbook(书),同时要指定文档的编码类型:\document......
  • Linux学习笔记
    Linux目录结构bin->usr/bin用于存放二进制命令boot内核及引导系统程序所在的目录  dev所有设备文件的目录(如磁盘、光驱等)etc配置文件默认路径、服务启动命令存放目录home用户家目录,root用户为/rootlib->usr/lib32位库文件存放目录lib64->usr/lib6464位库文......
  • Unity工具开发教程笔记(1/4)
    目录什么是Unity工具开发程序员FieldAttributes辅助图标Gizmos程序集Assembly和ExecuteInEditMode注解管理类ExplosiveBarrelManagerHandles类&预处理器贝塞尔曲线DrawingBezierCurvesMaterial&MeshModificationPitfalls材质属性块MaterialPropertyBlockScriptableOb......
  • [PHP](MD5、sha1)比较漏洞-笔记
    PhP(MD5、sha1)比较漏洞(弱比较、强比较、强碰撞)弱比较md5和sha1弱比较都是利用php解析哈希值以“0E”开头的特性,结果都为0符合参数1的字符串值和参数2的字符串值不相等,但md5值相等。如:240610708,aabg7XSs,aabC9RqS,s878926199a这四段字符串MD5编码后结果分别对应240610708:0E462097......