简单启发式合并

概述:有 N 个集合, 每个集合大小为 1, 只有合并两个集合的操作, 若每次合并两个集合把小集合的元素拿出来插入到大集合里, 那么对于小集合里的元素, 所在集合的大小翻倍了。

所以所有元素被插入的次数是 \(O(\log N)\) 的。


P3302

对于森林的每棵树, 用倍增数组维护 LCA, 维护到根的可持久化线段树。

于是查询的时候直接查, 连边的时候重构较小的那棵树。

代码不难。

P5290

好好读题分析的时候就不会痛苦了。

很多连续动作用 for 语句表示会很好看。

比如:

for (cnt = 0; q[y].size(); q[x].pop(), q[y].pop()) {
 o[cnt++] = std :: max(q[x].top(), q[y].top());
}

记录

P3224

用维护整数集合的东西启发式合并即可。

复习一下 treap。

一个 update siz 的小函数少加了一个 1 就调了这么长时间啊……

记录

struct node {
	int siz, val, rnd, c[2];
	node() {}
	node(int s, int v) : siz(s), val(v) {
		rnd = rand();
		c[0] = c[1] = 0;
	}
} t[N];
int tot, root;

inline void upd (int me) {
	t[me].siz = t[t[me].c[0]].siz + t[t[me].c[1]].siz + 1;
}

void ro (int & me, int dir) {
 int y = t[me].dir;
 t[me].c[dir] = t[y].c[dir ^ 1], t[y].c[dir ^ 1] = me;
 upd(me), upd(y);
 me = y;
}

void ins (int x, int & me) {
 if (!me) { t[me] = node(1, x); return; }
 ++ t[me].siz;
 int dir = x > t[me].val;
 ins (x, t[me].c[dir]);
 if (t[me].c[dir][t].rnd > t[me].rnd) ro (me, dir);
}

void del (int x, int & me) {
 if (!me) return ;
 if (t[me].val != x) {
  --t[me].siz;
  del (x, t[me].c[x > t[me].val]);
 } else {
  if (!t[me].c[0] || !t[me].c[1]) me = t[me].c[0] | t[me].c[1];
  else {
   int dir = t[t[me].c[1]].rnd > t[t[me].c[0]].rnd;
   ro (me, dir);
   --t[me].siz;
   del (x, t[me].c[dir ^ 1]);
  }
 }
}

P3402【可持久化并查集】

记录来看我清晰直观的代码!

CF600E【树上启发式合并】

直接用这题介绍了, 算法过程:

  1. 计算所有轻儿子贡献,并撤回所有插入
  2. 计算重儿子贡献,不对数据结构做任何操作
  3. 将轻儿子全部插入数据结构
  4. 回答当前子树询问,回溯

一个点作为轻儿子子树的点的次数是 \(O(\log N)\) 级别的。

记录

可以用 【DSU ON TREE】这种 coding style, 即依据 dfs 序的子树内连续的性质通过遍历 dfs 序来遍历子树中的结点。 会让生活变得轻松

CF741D

好长的题面耶。

刚开始读成了排序而不是重新排列?????????

由于启发式合并也有一个一个插入轻儿子的过程, 所以统计这个东西就很轻松了。

记录

不小心加了点蛇足,不过影响不大。

posted @ 2021-03-28 19:31  xwmwr  阅读(63)  评论(0编辑  收藏  举报