Description

给定一个 \(n\) 个节点的森林,有 \(Q\) 次操作,每次要么将森林中某两点联通,保证操作后还是个森林,要么查询两点间权值第 \(k\) 小,保证两点联通。强制在线。

Limitation

\(1~\leq~n,~Q~\leq~80000\)

Solution

考虑有连边还有查询链上第 \(k\) 大,于是要么用 LCT,要么用主席树。

考虑如果用 LCT 的话,并不能快速的维护两点间链的信息(其实感觉在access的时候乱搞一下有希望在多一个 \(\log\) 的代价下维护一颗权值线段树的,但是没有仔细想 ),但是如果使用主席树,在连边的时候可以考虑启发式合并,可以以多一个 \(\log\) 为代价快速合并两个森林。

其实这种合并森林信息的,大概一共就只有 LCT 和启发式合并两种做法吧……

与此类似的在一棵树上合并子树信息的大概只有启发式合并和静态树上链分治两种做法叭……当然不排除有毒瘤题把这个强行转化成子树和父节点连边然后用 LCT 做……启发式合并的例子比如[十二省联考2019]春节十二响,静态树上链分治的例子比如 [CF600E]Lomsat gelral。

于是使用主席树维护每个节点到根的权值线段树即可快速查询链上第 \(k\) 大,在合并森林的时候进行启发式合并。注意到用主席树求链上第 \(k\) 大需要用到两点间 LCA,对于 LCA 的维护可以启发式合并两个森林的倍增数组。合并次数是 \(O(\log n)\) 级别的,每次合并是 \(O(n \log n)\) 的,于是合并总复杂度 \(O(n \log^2 n)\),另外查询复杂度 \(O(q~\log n)\)。所以总的复杂度为 \(O(n~\log^2 n~+~q~\log n)\)。写起来也非常好写,相比于 [HNOI2016]树。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif typedef long long int ll; namespace IPT {
const int L = 1000000;
char buf[L], *front=buf, *end=buf;
char GetChar() {
if (front == end) {
end = buf + fread(front = buf, 1, L, stdin);
if (front == end) return -1;
}
return *(front++);
}
} template <typename T>
inline void qr(T &x) {
char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
if (lst == '-') x = -x;
} namespace OPT {
char buf[120];
} template <typename T>
inline void qw(T x, const char aft, const bool pt) {
if (x < 0) {x = -x, putchar('-');}
int top=0;
do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
while (top) putchar(OPT::buf[top--]);
if (pt) putchar(aft);
} const int maxn = 80005; int lstans, n, m, t;
int ufs[maxn], sz[maxn], MU[maxn], tmp[maxn], anc[18][maxn], depth[maxn];
bool vis[maxn]; struct Tree {
Tree *ls, *rs;
int l, r, v; ~Tree() {
if (this->ls) {
delete this->ls;
delete this->rs;
}
} Tree() : ls(NULL), rs(NULL), l(0), r(0), v(0) {}
};
Tree *rot[maxn]; struct Edge {
int v;
Edge *nxt; Edge(const int _v, Edge *h) : v(_v), nxt(h) {}
};
Edge *hd[maxn];
void cont(const int u, const int v) {
hd[u] = new Edge(v, hd[u]);
hd[v] = new Edge(u, hd[v]);
} void init_hash();
int find(const int x);
int GetLCA(int u, int v);
void dfs(const int u, const int rt);
void build(Tree *u, Tree *pre, const int v);
void buildzero(Tree *u, const int l, const int r);
int query(const Tree *const u, const Tree *const v, const Tree *const x, const Tree *const y, const int k); int main() {
freopen("1.in", "r", stdin);
qr(lstans); lstans = 0;
qr(n); qr(m); qr(t);
for (int i = 1; i <= n; ++i) {
qr(MU[i]);
}
init_hash();
buildzero(rot[0] = new Tree, 1, n);
for (int i = 1, u, v; i <= m; ++i) {
u = v = 0; qr(u); qr(v);
cont(u, v);
}
for (int i = 1; i <= n; ++i) if (!vis[i]) {
dfs(i, i);
}
int a, b, c;
while (t--) {
char op;
do op = IPT::GetChar(); while ((op != 'Q') && (op != 'L'));
if (op == 'Q') {
a = b = c = 0; qr(a); qr(b); qr(c);
a ^= lstans; b ^= lstans; c ^= lstans;
int k = GetLCA(a, b);
qw(lstans = tmp[query(rot[a], rot[b], rot[k], rot[anc[0][k]], c)], '\n', true);
} else {
a = b = 0; qr(a); qr(b);
a ^= lstans; b ^= lstans;
int fa = find(a), fb = find(b);
if (sz[fa] > sz[fb]) {
std::swap(a, b);
std::swap(fa, fb);
}
sz[fb] += sz[fa];
anc[0][a] = b;
dfs(a, fb);
cont(a, b);
}
}
return 0;
} void dfs(const int u, const int rt) {
vis[u] = true;
sz[u] = 1; ufs[u] = rt;
depth[u] = depth[anc[0][u]] + 1;
build(rot[u] = new Tree, rot[anc[0][u]], MU[u]);
for (int i = 0; i < 17; ++i) {
anc[i + 1][u] = anc[i][anc[i][u]];
}
for (auto e = hd[u]; e; e = e->nxt) if (e->v != anc[0][u]) {
anc[0][e->v] = u;
dfs(e->v, rt);
sz[u] += sz[e->v];
}
} void init_hash() {
memcpy(tmp + 1, MU + 1, n << 2);
std::sort(tmp + 1, tmp + 1 + n);
auto ed = std::unique(tmp + 1, tmp + 1 + n);
for (int i = 1; i <= n; ++i) {
MU[i] = std::lower_bound(tmp + 1, ed, MU[i]) - tmp;
}
} void buildzero(Tree *u, const int l, const int r) {
if ((u->l = l) == (u->r = r)) return;
int mid = (l + r) >> 1;
buildzero(u->ls = new Tree, l, mid); buildzero(u->rs = new Tree, mid + 1, r);
} void build(Tree *u, Tree *pre, const int v) {
*u = *pre; ++u->v;
if (u->l == u->r) return;
if (u->ls->r >= v) {
build(u->ls = new Tree, pre->ls, v);
} else {
build(u->rs = new Tree, pre->rs, v);
}
} int GetLCA(int u, int v) {
if (depth[u] > depth[v]) {
std::swap(u, v);
}
int delta = depth[v] - depth[u];
for (int i = 17; delta; --i) if (delta >= (1 << i)) {
delta -= 1 << i;
v = anc[i][v];
}
if (u == v) return u;
for (int i = 17; ~i; --i) if (anc[i][u] != anc[i][v]) {
u = anc[i][u]; v = anc[i][v];
}
return anc[0][v];
} int query(const Tree *const u, const Tree *const v, const Tree *const x, const Tree *const y, const int k) {
if (u->l == u->r) return u->l;
int lv = u->ls->v + v->ls->v - x->ls->v - y->ls->v;
if (lv >= k) {
return query(u->ls, v->ls, x->ls, y->ls, k);
} else {
return query(u->rs, v->rs, x->rs, y->rs, k - lv);
}
} inline int find(const int x) {
return ufs[x] == x ? x : ufs[x];
}

Summary

其实这种合并森林信息的,大概一共就只有 LCT 和启发式合并两种做法吧……

与此类似的在一棵树上合并子树信息的大概只有启发式合并和静态树上链分治两种做法叭……

后面想到会再更新的

【主席树启发式合并】【P3302】[SDOI2013]森林的更多相关文章

  1. P3302 [SDOI2013]森林(主席树+启发式合并)

    P3302 [SDOI2013]森林 主席树+启发式合并 (我以前的主席树板子是错的.......坑了我老久TAT) 第k小问题显然是主席树. 我们对每个点维护一棵包含其子树所有节点的主席树 询问(x ...

  2. BZOJ_3123_[Sdoi2013]森林_主席树+启发式合并

    BZOJ_3123_[Sdoi2013]森林_主席树+启发式合并 Description Input 第一行包含一个正整数testcase,表示当前测试数据的测试点编号.保证1≤testcase≤20 ...

  3. Bzoj 3123: [Sdoi2013]森林(主席树+启发式合并)

    3123: [Sdoi2013]森林 Time Limit: 20 Sec Memory Limit: 512 MB Description Input 第一行包含一个正整数testcase,表示当前 ...

  4. 【主席树 启发式合并】bzoj3123: [Sdoi2013]森林

    小细节磕磕碰碰浪费了半个多小时的时间 Description Input 第一行包含一个正整数testcase,表示当前测试数据的测试点编号.保证1≤testcase≤20. 第二行包含三个整数N,M ...

  5. [bzoj3123] [SDOI2013]森林 主席树+启发式合并+LCT

    Description Input 第一行包含一个正整数testcase,表示当前测试数据的测试点编号.保证1≤testcase≤20. 第二行包含三个整数N,M,T,分别表示节点数.初始边数.操作数 ...

  6. 【bzoj3123】[Sdoi2013]森林 倍增LCA+主席树+启发式合并

    题目描述 输入 第一行包含一个正整数testcase,表示当前测试数据的测试点编号.保证1≤testcase≤20. 第二行包含三个整数N,M,T,分别表示节点数.初始边数.操作数.第三行包含N个非负 ...

  7. 【BZOJ-3123】森林 主席树 + 启发式合并

    3123: [Sdoi2013]森林 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 2738  Solved: 806[Submit][Status] ...

  8. Bzoj2534:后缀自动机 主席树启发式合并

    国际惯例的题面:考虑我们求解出字符串uvu第一个u的右端点为i,第二个u的右端点为j,我们需要满足什么性质?显然j>i+L,因为我们选择的串不能是空串.另外考虑i和j的最长公共前缀(也就是说其p ...

  9. Bzoj 3673: 可持久化并查集 by zky(主席树+启发式合并)

    3673: 可持久化并查集 by zky Time Limit: 5 Sec Memory Limit: 128 MB Description n个集合 m个操作 操作: 1 a b 合并a,b所在集 ...

随机推荐

  1. 初识Go语言--(1)环境安装

    1.安装包下载:https://golang.google.cn/dl/ 2. 一直点击下一步进行安装 3.安装后查看版本及帮助信息

  2. SQL-----数据库三种删除方式详解

    第一种  使用delete  语句 特点: delete 属于数据库操纵语言DML,表示删除表中的数据, 删除过程是每次从表中删除一行,并把该行删除操作作为事务记录在日志中保存 可以配合事件(tran ...

  3. golang 学习笔记 ---JSON

    JSON解析到结构体 在介绍这部分之前先简要介绍一下Json语法 JSON 语法是 JavaScript 语法的子集.JSON 语法是 JavaScript 对象表示法语法的子集. 数据在名称/值对中 ...

  4. 【ELK】elasticsearch使用bulk 导入批量的数据集文件报错:Validation Failed: 1: no requests added

    执行命令如下: curl -XPOST http://192.168.6.16:9200/my_new_index/user/_bulk?pretty --data-binary @/cjf/es/e ...

  5. Java8 Optional总结

    分类专栏: Java   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/HEYUTAO00 ...

  6. 外网IP和内网IP区别

    外网IP和内网IP区别? 网络结构 如图,假设我们的计算机现在就是设备一,我们想要访问百度.如果我们正使用着校园网,那么首先我们需要先通过校园网的路由器把我们的内网ip转为校园网的外网ip.然后通过这 ...

  7. C#常用集合类的实现以及基本操作复杂度

    List 集合类是顺序线性表,Add操作是O(1)或是O(n)的,由于List的容量是动态扩容的,在未扩容之前,其Add操作是O(1),而在需要扩容的时候,会拷贝已存在的那些元素同时添加新的元素,此时 ...

  8. vue+element表单校验功能

    要实现这个功能其实并不难,element组件直接用就可以, 但是我在使用过程中碰到了几个坑,就记录下来,分享给大家,避免落坑,话不多说,直接上过程...... 表单校验功能:   实现这个功能,总共分 ...

  9. maven 学习---Maven本地资源库

    Maven的本地资源库是用来存储所有项目的依赖关系(插件jar和其他文件,这些文件被Maven下载)到本地文件夹. 很简单,当你建立一个Maven项目,所有相关文件将被存储在你的Maven本地仓库. ...

  10. Hybris Storefront里产品图片显示不出来的分析方法

    在Hybris Backoffice里新建一个product,维护了一些图片信息,然而在Storefront上进行搜索时,无论结果列表,还是产品明细页面里,都没有对应的图片可以显示. 我的排查方法是, ...