【笔记】启发式合并

考虑有 n 个各包含 1 个元素的集合,每次任选两个集合合并,合并方式为将小的集合的元素一个个扔到大的集合
这样做的复杂度是 \(O(nlogn)\)
考虑将合并过程表示成一个二叉树,一个节点对应合并过程中的一个集合,标记每个被合并到大集合的小集合,总的合并次数就是每个初始元素到根节点被标记的次数之和。注意到每次一个集合被标记时,集合大小至少会翻倍,故每个元素最多经历 logn 次标记(合并),总的是 \(O(nlogn)\)
于是我们可以按这种方式暴力合并一些数据结构,比如并查集,线段树(虽然本来就有线段树合并),set,甚至只是一个数组之类的

CCPC2020 Changchun K
写 T 竟是因为用 multiset 而不是 set 存数值和个数,导致一个一个挪的大常数

dsu on tree 树上启发式合并,对于当前节点,能得到全局数据结构仅包含当前节点的子树的信息
CCPC-Final 2019 K
求每个节点的子树的编号构成的连续段的个数

void insert(int x, int fa)
{
	add(x);
	for (int o=fst[x]; o; o=e[o].pre) if (e[o].v!=fa) insert(e[o].v, x);
}
void remove(int x, int fa)
{
	del(x);
	for (int o=fst[x]; o; o=e[o].pre) if (e[o].v!=fa) remove(e[o].v, x);
}
void dfs(int x, int fa)
{
	int p = 0;
	for (int o=fst[x]; o; o=e[o].pre) if (e[o].v!=fa) {
		if (sz[e[o].v]> sz[p]) p = e[o].v;
	}
	for (int o=fst[x]; o; o=e[o].pre) if (e[o].v!=fa) {
		if (e[o].v==p) continue;
		dfs(e[o].v, x), remove(e[o].v, x);
	}
	if (p) dfs(p, x);
	for (int o=fst[x]; o; o=e[o].pre) if (e[o].v!=fa) {
		if (e[o].v==p) continue;
		insert(e[o].v, x);
	}
	add(x), ansn[x] = ans;
}

CCPC2020 Changchun Onsite 维护子树点权二进制拆分的信息(以及统计答案的技巧)

posted @ 2021-10-08 14:34  zrkc  阅读(63)  评论(0)    收藏  举报