【笔记】启发式合并
考虑有 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 维护子树点权二进制拆分的信息(以及统计答案的技巧)

浙公网安备 33010602011771号