题目大意:维护 N 个点的无向图,支持动态加边和删边,回答两点的连通性。

题解:线段树分治 + 可撤销并查集

询问可以离线,这是线段树分治的基础。

建立在操作时间轴上的线段树称为线段树分治算法。

本题中线段树维护的是当前时间段中出现的边的集合。分析可知,对于一条边来说,至多出现在线段树上 \(O(logm)\) 个节点的集合中,至多 \(M\) 条边,因此,线段树上的边集合大小一共为 \(O(mlogm)\)。建立好线段树之后,从根开始 dfs 整棵树,每经过一个节点时,将当前时间区间内出现的边加入并查集中,并递归到子树;离开当前节点之前,需要将当前节点边集中的边从并查集中删除。若到了时间区间的叶子节点,则回答对应时间的询问即可。带撤销的并查集采用按秩合并,单次操作的时间复杂度为 \(O(logn)\)。故总的时间复杂度为 \(O(mlogmlogn)\)。

代码如下

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
const int maxm = 2e5 + 10;
typedef pair<int, int> PI; int n, m;
map<PI, int> mp;
map<int, PI> qry;
vector<int> ans; struct node {
int x, y, rx, ry;
};
stack<node> stk;
int fa[maxn], rk[maxn];
int find(int x) {
while (x != fa[x]) {
x = fa[x];
}
return x;
}
void merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) {
return;
}
if (rk[x] > rk[y]) {
swap(x, y);
}
stk.push(node {x, y, rk[x], rk[y]});
fa[x] = y;
if (rk[x] == rk[y]) {
++rk[y];
}
}
void undo() {
node t = stk.top();
fa[t.x] = t.x, fa[t.y] = t.y;
rk[t.x] = t.rx, rk[t.y] = t.ry;
stk.pop();
}
int ask(int x, int y) {
return find(x) == find(y) ? 1 : 2;
} struct SegTree {
#define ls(o) t[o].lc
#define rs(o) t[o].rc
int lc, rc;
vector<PI> s;
} t[maxm << 1];
int rt;
void build(int &o, int l, int r) {
static int tot = 0;
o = ++tot;
if (l == r) {
return;
}
int mid = l + r >> 1;
build(ls(o), l, mid);
build(rs(o), mid + 1, r);
}
void insert(int o, int l, int r, int x, int y, PI z) {
if (l == x && r == y) {
t[o].s.push_back(z);
return;
}
int mid = l + r >> 1;
if (y <= mid) {
insert(ls(o), l, mid, x, y, z);
} else if (x > mid) {
insert(rs(o), mid + 1, r, x, y, z);
} else {
insert(ls(o), l, mid, x, mid, z);
insert(rs(o), mid + 1, r, mid + 1, y, z);
}
}
void dfs(int o, int l, int r) {
for (auto e : t[o].s) {
merge(e.first, e.second);
}
if (l == r) {
if (qry.find(l) != qry.end()) {
PI q = qry[l];
ans.push_back(ask(q.first, q.second));
}
for (auto e : t[o].s) {
undo();
}
return;
}
int mid = l + r >> 1;
dfs(ls(o), l, mid);
dfs(rs(o), mid + 1, r);
for (auto e : t[o].s) {
undo();
}
} int main() {
static char s[10];
scanf("%d %d", &n, &m); for (int i = 1; i <= n; i++) {
fa[i] = i, rk[i] = 1;
}
build(rt, 1, m); for (int i = 1, x, y; i <= m; i++) {
scanf("%s %d %d", s, &x, &y);
if (x > y) {
swap(x, y);
}
if (s[0] == 'C') {
mp[PI(x, y)] = i;
} else if (s[0] == 'D') {
auto p = mp.find(PI(x, y));
assert(p != mp.end());
insert(rt, 1, m, p->second, i, p->first);
mp.erase(p);
} else {
qry[i] = PI(x, y);
}
}
for (auto e : mp) {
insert(rt, 1, m, e.second, m, e.first);
}
dfs(rt, 1, m);
for (int i = 0; i < ans.size(); i++) {
puts(ans[i] == 1 ? "Yes" : "No");
}
return 0;
}

【洛谷P2147】洞穴勘测的更多相关文章

  1. 洛谷P2147[SDOI2008]洞穴勘测(lct)

    题目描述 辉辉热衷于洞穴勘测. 某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好两个洞穴.假 ...

  2. 洛谷P2147 [SDOI2008]Cave 洞穴勘测

    题目描述 辉辉热衷于洞穴勘测. 某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好两个洞穴.假 ...

  3. 洛谷P2147 [SDOI2008] 洞穴勘探 [LCT]

    题目传送门 洞穴勘探 题目描述 辉辉热衷于洞穴勘测. 某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道 ...

  4. [洛谷P2147][SDOI2008]洞穴勘测

    题目大意:有$n$个洞穴,$m$条指令,指令有三种 $Connect\;u\;v$:在$u,v$之间连一条边 $Destroy\;u\;v$:切断$u,v$之间的边 $Query\;u\;v$:询问$ ...

  5. 【洛谷 P2147】 [SDOI2008]洞穴勘测(LCT)

    题目链接 LCT裸题.. #include <cstdio> #define R register int #define I inline void #define lc c[x][0] ...

  6. 洛谷 P2147 [SDOI2008]洞穴勘测 (线段树分治)

    题目链接 题解 早就想写线段树分治的题了. 对于每条边,它存在于一段时间 我们按时间来搞 我们可把一条边看做一条线段 我们可以模拟线段树操作,不断分治下去 把覆盖\(l-r\)这段时间的线段筛选出来, ...

  7. 洛谷 P2147 [SDOI2008]洞穴勘测

    以下这个做法应该是叫线段树分治... 根据修改操作预处理出每条边存在的时间区间[l,r](以操作序号为时间),然后把所有形式化后的修改挂到线段树节点上. 处理完修改后,dfs一遍线段树,进入某个节点时 ...

  8. 题解 洛谷P2147/BZOJ2049【[SDOI2008]洞穴勘测】

    Link-Cut-Tree的模板题啊......(听说还可以用其他的方法做,不管了,直接上LCT) 没有要求维护点权,只需要维护点的连通性即可. 就是朴素的LCT,居然还不要pushup. 感觉有些不 ...

  9. 洛谷 P2147 [SDOI2008]洞穴勘测 LCT

    Code: #include <cstdio> #include <algorithm> #include <string> #include <cstrin ...

随机推荐

  1. java-字符

    注意第16行处理方式.

  2. 【机器学习】Jackknife,Bootstraping, bagging, boosting, AdaBoosting, Rand forest 和 gradient boosting

    Jackknife,Bootstraping, bagging, boosting, AdaBoosting, Rand forest 和 gradient boosting 这些术语,我经常搞混淆, ...

  3. IIS调试

    点击调试程序的时候默认启动TFS内置的调试服务器http://localhost:51607/,在该处填写自己在IIS上配置的站点就能进入自己的站点调试http://mr.ciwong.com

  4. EL表达式的11隐含对象

    EL表达式在不同范围如何取值: <% pageContext.setAttribute("book", "红楼梦"); request.setAttrib ...

  5. ADG环境搭建

    一:实验环境介绍PC机系统: CentOS 6.5(64位)数据库版本: Oracle 11gR2 11.2.0.4 (64位)IP地址规划:主数据库10.110.9.41 SID:orapridb_ ...

  6. .Net Core控制台应用加载读取Json配置文件

    ⒈添加依赖 Microsoft.Extensions.Configuration Microsoft.Extensions.Configuration.FileExtensions Microsoft ...

  7. 为什么说Python采用的是基于值的内存管理模式?

    Python中的变量并不直接存储值,而是存储了值的内存地址或者引用,假如为不同变量赋值为相同值,这个值在内存中只有一份,多个变量指向同一块内存地址.

  8. O009、KVM 网络虚拟化基础

    参考https://www.cnblogs.com/CloudMan6/p/5289590.html   网络虚拟化是虚拟化技术中最复杂的部分,学习难度最大.   但因为网络是虚拟化中非常重要的资源, ...

  9. 详解EveryThing

    摘要:Everything几乎是每个职场人必备的效率工具,但同事们都只用它的一两个基本功能,并没有发挥出该软件的真正效率.实际上,把Everything的功能用到极致能够成倍的提升我们的工作效率,本文 ...

  10. 把两个object对象合并成一个对象 属性名称相同的变为后面对象的值

    object.assign(from,obj)------object.assign(目标对象,被合并的对象)