题目大意:维护 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. Tensorflow 从文件中载入训练数据

    本节包含: 用纯文本文件准备训练数据 加载文件中的训练数据 一.用纯文本文件准备训练数据 1.数据的数字化 比如,“是” —— “1”,“否” —— “0” “优”,“中”,“差” —— 1 2 3  ...

  2. Android开发实例 Unity显示Toast

    Android中的Toast是一种简易的消息提示框. 当视图显示给用户,在应用程序中显示为浮动.和Dialog不一样的是,它永远不会获得焦点,无法被点击.用户将可能是在中间键入别的东西.Toast类的 ...

  3. webdriervAPI(常用的js方法)

    from  selenium  import  webdriver driver  =  webdriver.Chorme() driver.get("http://www.baidu.co ...

  4. WCf客户端测试

    添加项目ConsoleWCFTest 添加WCFService.WCFServiceProxy 配置App.config <?xml version="1.0" encodi ...

  5. NModbus4的使用

    步骤1:打开串口 SerialPort port = new SerialPort("COM7") port.BaudRate = ; port.BaudRate = ; port ...

  6. shell作业01

    1.判断/etc/inittab文件是否大于100行,如果大于,则显示”/etc/inittab is a big file.”否者显示”/etc/inittab is a small file.” ...

  7. Django2.2 连接mySQL数据库

    一.Django2.2连接数据库(踩雷) 首先,Django2.2自带的是sqlite3数据库,但我们学的是mysql,因此学着连接(在mysql环境搭建成功的情况下)---参考博客: https:/ ...

  8. Linux学习笔记(16)Linux前后台进程切换(fg/bg/jobs/ctrl+z)

    关键词:Linux前后台进程切换,linux进程切换 fg.bg.jobs.&.ctrl + z都是跟系统任务有关的,虽然现在基本上不怎么需要用到这些命令,但学会了也是很实用的一.& ...

  9. (5.4)mysql高可用系列——MySQL异步复制(实践)

    关键词:mysql复制,mysql异步复制,mysql传统异步复制 [1]实验环境 操作系统:CentOS linux 7.5 数据库版本:5.7.24 数据库架构:主从复制,主库用于生产,从库用于数 ...

  10. Shell初学(五)bash shell的基本功能

    记住,所谓的bash shell 并不单纯指的是shell脚本,其实是Linux系统的所有指令集. shell脚本 只是汇总了指令集到文件,然后按流程和顺序执行. [1]如何查看我们的预设shell ...