题面

2049: [Sdoi2008]Cave 洞穴勘测

Time Limit: 10 Sec Memory Limit: 259 MB

Submit: 12030 Solved: 6024

Description

辉辉热衷于洞穴勘测。某天,他按照地图来到了一片被标记为JSZX的洞穴群地区。经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好两个洞穴。假如两个洞穴可以通过一条或者多条通道按一定顺序连接起来,那么这两个洞穴就是连通的,按顺序连接在一起的这些通道则被称之为这两个洞穴之间的一条路径。洞穴都十分坚固无法破坏,然而通道不太稳定,时常因为外界影响而发生改变,比如,根据有关仪器的监测结果,123号洞穴和127号洞穴之间有时会出现一条通道,有时这条通道又会因为某种稀奇古怪的原因被毁。辉辉有一台监测仪器可以实时将通道的每一次改变状况在辉辉手边的终端机上显示:如果监测到洞穴u和洞穴v之间出现了一条通道,终端机上会显示一条指令 Connect u v 如果监测到洞穴u和洞穴v之间的通道被毁,终端机上会显示一条指令 Destroy u v 经过长期的艰苦卓绝的手工推算,辉辉发现一个奇怪的现象:无论通道怎么改变,任意时刻任意两个洞穴之间至多只有一条路径。因而,辉辉坚信这是由于某种本质规律的支配导致的。因而,辉辉更加夜以继日地坚守在终端机之前,试图通过通道的改变情况来研究这条本质规律。然而,终于有一天,辉辉在堆积成山的演算纸中崩溃了……他把终端机往地面一砸(终端机也足够坚固无法破坏),转而求助于你,说道:“你老兄把这程序写写吧”。辉辉希望能随时通过终端机发出指令 Query u v,向监测仪询问此时洞穴u和洞穴v是否连通。现在你要为他编写程序回答每一次询问。已知在第一条指令显示之前,JSZX洞穴群中没有任何通道存在。

Input

第一行为两个正整数n和m,分别表示洞穴的个数和终端机上出现过的指令的个数。以下m行,依次表示终端机上出现的各条指令。每行开头是一个表示指令种类的字符串s("Connect”、”Destroy”或者”Query”,区分大小写),之后有两个整数u和v (1≤u, v≤n且u≠v) 分别表示两个洞穴的编号。

Output

对每个Query指令,输出洞穴u和洞穴v是否互相连通:是输出”Yes”,否则输出”No”。(不含双引号)

Sample Input

样例输入1 cave.in

200 5

Query 123 127

Connect 123 127

Query 123 127

Destroy 127 123

Query 123 127

样例输入2 cave.in

3 5

Connect 1 2

Connect 3 1

Query 2 3

Destroy 1 3

Query 2 3

Sample Output

样例输出1 cave.out

No

Yes

No

样例输出2 cave.out

Yes

No

HINT

数据说明 10%的数据满足n≤1000, m≤20000 20%的数据满足n≤2000, m≤40000 30%的数据满足n≤3000, m≤60000 40%的数据满足n≤4000, m≤80000 50%的数据满足n≤5000, m≤100000 60%的数据满足n≤6000, m≤120000 70%的数据满足n≤7000, m≤140000 80%的数据满足n≤8000, m≤160000 90%的数据满足n≤9000, m≤180000 100%的数据满足n≤10000, m≤200000 保证所有Destroy指令将摧毁的是一条存在的通道本题输入、输出规模比较大,建议c\c++选手使用scanf和printf进行I\O操作以免超时

分析

一条边的存在的时间其实就是一段连续的时间(我们这里指定每一行命令就是一个单位的时间),这样我们就可以把问题离线化。

那么我们可以用线段树来保存整个状态,这并不是什么难事,我们在线段树的每一个节点上保存一个边的集合,表示这个节点下所有的子节点都包含了这条边。

那么我们对于任意一个叶子节点,从根节点到叶子节点的全过程遍历到的所有的边组成的集合就是当前的图

由于如果每次询问都是从根节点出发的话效率太低,我们采用直接在线段树上移动的方式来解决效率问题。

通过可撤销并查集的性质,来实现在线段树上移动

AC code

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 10100;
const int MAXM = 200100; typedef pair<int, int> pii; struct UFS {
int f[MAXN];
stack<pii> s; int finds(int x) {
while (x ^ f[x])
x = f[x];
return x;
} void unite(int x, int y) {
x = finds(x);
y = finds(y);
if (x != y) {
s.push({x, f[x]});
f[x] = y;
}
} void init(int b, int e) { // 初始化函数,范围为 [b, e)
for (int i = b; i < e; i++)
f[i] = i;
} void undo() {
f[s.top().first] = s.top().second;
s.pop();
}
}; struct SegTree {
vector<pii> data[MAXM << 2]; static inline int lson(int k) { return k << 1; } static inline int rson(int k) { return (k << 1) | 1; } static inline int fat(int l, int r) { return (l + r) >> 1; }
// add 函数对应于正常的线段树的 insert,但是稍微有些不同
void add(int k, int l, int r, int x, int y, const pii &value) {
if (l == x && r == y) {
data[k].push_back(value);
return;
}
int mid = fat(l, r);
if (y <= mid) {
add(lson(k), l, mid, x, y, value);
} else if (x > mid) {
add(rson(k), mid + 1, r, x, y, value);
} else {
add(lson(k), l, mid, x, mid, value);
add(rson(k), mid + 1, r, mid + 1, y, value);
}
}
}; UFS ufs;
SegTree segTree;
vector<pair<pii, int> > que;
int tar;
// 通过 dfs 的方式在线段树上移动
bool dfs(int k, int l, int r) {
// 当完成一次询问之后,需要跳出当前的叶子,即回溯。通过 goto 来使得回溯的过程会自动进入正确的叶子节点
rejudge:
int target = que[tar].second;
if (target == r && l == r) {
if (ufs.finds(que[tar].first.first) == ufs.finds(que[tar].first.second))
cout << "Yes" << endl;
else
cout << "No" << endl;
tar++;
return tar == que.size();// true 表示所有的询问已经结束,退出 dfs
}
int mid = SegTree::fat(l, r);
if (target <= mid) {
// for (auto &item: segTree.data[SegTree::lson(k)])
// ufs.unite(item.first, item.second);
for (int i = 0; i < segTree.data[SegTree::lson(k)].size(); ++i)
ufs.unite(segTree.data[SegTree::lson(k)][i].first, segTree.data[SegTree::lson(k)][i].second);
if (dfs(SegTree::lson(k), l, mid))
return true;
// for (auto &item: segTree.data[SegTree::lson(k)])
for (int i = 0; i < segTree.data[SegTree::lson(k)].size(); ++i)
ufs.undo();
if (que[tar].second > r)
return false;
goto rejudge;
} else {
// for (auto &item: segTree.data[SegTree::rson(k)])
// ufs.unite(item.first, item.second);
for (int i = 0; i < segTree.data[SegTree::rson(k)].size(); ++i)
ufs.unite(segTree.data[SegTree::rson(k)][i].first, segTree.data[SegTree::rson(k)][i].second);
if (dfs(SegTree::rson(k), mid + 1, r))
return true;
// for (auto &item: segTree.data[SegTree::rson(k)])
for (int i = 0; i < segTree.data[SegTree::rson(k)].size(); ++i)
ufs.undo();
if (que[tar].second > r)
return false;
goto rejudge;
}
} void solve() {
int n, m;
cin >> n >> m;
map<pii, int> mp;
for (int i = 0; i < m; ++i) {
string s;
int u, v;
cin >> s >> u >> v;
if (u > v) swap(u, v);
switch (s[0]) {
case 'Q':
// que.push_back({{u, v}, i + 1});
que.push_back(make_pair(make_pair(u, v), i + 1));
break;
case 'C':
// mp.insert({{u, v}, i + 1});
mp.insert(make_pair(make_pair(u, v), i + 1));
break;
case 'D': {
// auto iter = mp.find({u, v});
map<pii, int>::iterator iter = mp.find(make_pair(u, v));
// segTree.add(1, 1, m, iter->second, i, {u, v});
segTree.add(1, 1, m, iter->second, i, make_pair(u, v));
mp.erase(iter);
break;
}
}
}
map<pii, int>::iterator iter = mp.begin();
while (iter != mp.end()) {
segTree.add(1, 1, m, iter->second, m, iter->first);
iter++;
}
ufs.init(0, n + 1);
tar = 0;
// for (auto &item: segTree.data[1])
for (int i = 0; i < segTree.data[1].size(); ++i)
ufs.unite(segTree.data[1][i].first, segTree.data[1][i].second);
// ufs.unite(item.first, item.second);
dfs(1, 1, m);
return;
} int main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
// cin.tie(nullptr);
// cout.tie(nullptr);
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
long long test_index_for_debug = 1;
char acm_local_for_debug;
while (cin >> acm_local_for_debug) {
if (acm_local_for_debug == '$') exit(0);
cin.putback(acm_local_for_debug);
if (test_index_for_debug > 20) {
throw runtime_error("Check the stdin!!!");
}
auto start_clock_for_debug = clock();
solve();
auto end_clock_for_debug = clock();
cout << "Test " << test_index_for_debug << " successful" << endl;
cerr << "Test " << test_index_for_debug++ << " Run Time: "
<< double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
}
#else
solve();
#endif
return 0;
}

【bzoj2049】[Sdoi2008]Cave 洞穴勘测——线段树上bfs求可撤销并查集的更多相关文章

  1. BZOJ2049 SDOI2008 Cave 洞穴勘测 【LCT】

    BZOJ2049 SDOI2008 Cave 洞穴勘测 Description 辉辉热衷于洞穴勘测.某天,他按照地图来到了一片被标记为JSZX的洞穴群地区.经过初步勘测,辉辉发现这片区域由n个洞穴(分 ...

  2. 【LCT】BZOJ2049 [SDOI2008]Cave 洞穴勘测

    2049: [Sdoi2008]Cave 洞穴勘测 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 10059  Solved: 4863[Submit ...

  3. [BZOJ2049][Sdoi2008]Cave 洞穴勘测 LCT模板

    2049: [Sdoi2008]Cave 洞穴勘测 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 9705  Solved: 4674[Submit] ...

  4. [bzoj2049][Sdoi2008]Cave 洞穴勘测_LCT

    Cave 洞穴勘测 bzoj-2049 Sdoi-2008 题目大意:维护一个数据结构,支持森林中加边,删边,求两点连通性.n个点,m个操作. 注释:$1\le n\le 10^4$,$1\le m\ ...

  5. [BZOJ2049] [SDOI2008] Cave 洞穴勘测 (LCT)

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

  6. [bzoj2049][Sdoi2008]Cave 洞穴勘测——lct

    Brief Description 给定一个森林,您需要支持两种操作: 链接两个节点. 断开两个节点之间的链接. Algorithm Design 对于树上的操作,我们现在已经有了树链剖分可以处理这些 ...

  7. BZOJ2049: [Sdoi2008]Cave 洞穴勘测 Link-Cut-Tree 模板题

    传送门 搞了这么长时间Splay终于可以搞LCT了,等等,什么是LCT? $LCT$就是$Link-Cut-Tree$,是维护动态树的一个很高效的数据结构,每次修改和查询的均摊复杂度为$O(logN) ...

  8. BZOJ2049——[Sdoi2008]Cave 洞穴勘测

    1.题目大意:就是一个动态维护森林联通性的题 2.分析:lct模板题 #include <stack> #include <cstdio> #include <cstdl ...

  9. bzoj2049: [Sdoi2008]Cave 洞穴勘测

    lct入门题? 得换根了吧TAT 这大概不是很成熟的版本.. #include<iostream> #include<cstring> #include<cstdlib& ...

随机推荐

  1. List.remove()的使用注意

    不使用forEach的循环 使用forEach循环 参考 今天修改一个bug,需要取一个List和一个Set的交集,使用了双重循环.想着提高循环效率,每加入一个交集中的元素,就将List中的元素删除, ...

  2. angular知识点(2)

    angular知识点(2) 1.为了代码规范,对于需要自动加载的依赖,需要在前面加上注释,注释为://@ngInject 或者是/*@ngInject*/ 2.ngSwitch的应用 在需要用到选择出 ...

  3. JavaScript 语言精粹笔记3

    方法 毒瘤 糟粕 记录一下阅读蝴蝶书的笔记,本篇为书中最后一部分:方法.代码风格.优美的特性.毒瘤.糟粕等. 方法 这一章主要介绍了一些方法集.这里写几个我不太熟悉的方法和要点吧. array.joi ...

  4. 让百度和google收录我们的网站

    花了几天时间终于把这个看似高大上的博客搞好了,但是发现只能通过在地址栏输入地址进行访问,这很明显和我装X装到底的性格,于是乎在查阅了嘟爷的博客,和我各种百度终于搞出来了. 让谷歌收录 让谷歌收录还是比 ...

  5. 记 MySQL优化 20条

    1. 为查询缓存优化你的查询 大多数的MySQL服务器都开启了查询缓存.这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的.当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一 ...

  6. 先搞清楚这些问题,简历上再写你熟悉Java!

    原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 系列文章介绍 本文是<五分钟学Java>系列文章的一篇 本系列文章主要围绕Java程序员必须掌握的核心技能,结合我个人三年 ...

  7. python大佬养成计划----HTML网页设计(序列)

    序列化标签 1.有序标签--ol和li 有序列表标签是<ol>,是一个双标签.在每一个列表项目前要使用<li>标签.<ol>标签的形式是带有前后顺序之分的编号.如果 ...

  8. [LeetCode] 1103. Distribute Candies to People 分糖果

    题目: 思路: 本题一开始的思路就是按照流程一步步分下去,算是暴力方法,在官方题解中有利用等差数列进行计算的 这里只记录一下自己的暴力解题方式 只考虑每次分配的糖果数,分配的糖果数为1,2,3,4,5 ...

  9. 安装mysql.so

    1.----   cd /usr/local/src/php-5.5.34/ext/mysql/2.----  /usr/local/php5/bin/phpize3.---- ./configure ...

  10. Falsk 路由简析

    添加路由 我们熟知添加路由的方式是装饰器: @app.route('/') def hello_world(): return 'Hello World!' #访问web得到 'Hello World ...