ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[SDOI2008] 洞穴勘测

2021-08-17 10:00:07  阅读:225  来源: 互联网

标签:勘测 return int siz 线段 查集 洞穴 ma SDOI2008


前言

在我做这道题之前,我不会线段树分治和带撤销并查集,但是卷爷一下子就给我讲懂了。

也许做 岛屿探险 的时候我自己发明了线段树分治?

反正我现在学懂了。

题目

洛谷

讲解

LCT 板子题。

好吧,由于蒟蒻笔者不会 LCT,就只能换个角度思考问题。

检查连通性我们可以想到优秀的数据结构:并查集,而带删除我们可以想到带撤销并查集

但是有个问题,带撤销并查集必须是按类似栈的顺序撤销并查集中的边才行,而不能是按照题目中的操作顺序。

这可咋整?

于是我们想到线段树分治

对于每条边,我们可以将其出现过的时间找出来,可以发现这一定是个区间。

然后将这条边加入线段树中,可以发现时间和空间都是 \(\log_2m\) 级别的,可以接受。

最后直接 dfs 整棵线段树即可。每走到一个节点,将这个节点上的所有边加入并查集,退出这个点的时候将这些边撤销掉。

我们可以发现这个做法对于并查集来说,是按照加边顺序倒序撤销,正确。

而对于询问来说,我们由于用线段树的区间代替时间,直接 dfs,时间上也是正确的。

时间复杂度 \(O(m\log_2m\log_2n)\),但由于其常数极小,所以可以与一个 \(\log\) 的 LCT 相媲美。

当然我说的常数小是相对于 LCT 来说的。

代码

//12252024832524
#include <map>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 200005;
int n,m;
char opt[10];

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

int f[MAXN],siz[MAXN];

struct node
{
	int u,v,l,r;
	bool f;
	node(){}
	node(int u1,int v1,int l1,int r1,bool f1){
		u = u1;
		v = v1;
		l = l1;
		r = r1;
		f = f1;
	}
};
#define lc (x<<1)
#define rc (x<<1|1)
vector<node> rt;
map<pair<int,int>,int> ma;

int s[MAXN][2],t;
int findSet(int x)
{
	if(f[x] ^ x) return findSet(f[x]);
	return x;
}
void unionSet(int u,int v)
{
	int U = findSet(u),V = findSet(v);
	if(U == V) return;
	if(siz[U] > siz[V]) swap(U,V);
	f[U] = V; siz[V] += siz[U];
	++t;
	s[t][0] = U,s[t][1] = V;
}
void dfs(int x,int l,int r,vector<node> &now)
{
	int tmp = t,mid = (l+r) >> 1;
	vector<node> L,R;
	for(int i = 0,len = now.size();i < len;++ i)
	{
		if(now[i].l <= l && r <= now[i].r)//完全覆盖 
		{
			if(!now[i].f) unionSet(now[i].u,now[i].v);
		}
		else
		{
			if(now[i].l <= mid) L.push_back(now[i]);
			if(mid+1 <= now[i].r) R.push_back(now[i]);
		}
	}
	if(l == r)
	{
		for(int i = 0,len = now.size();i < len;++ i)
			if(now[i].f)
			{
				if(findSet(now[i].u) == findSet(now[i].v)) printf("Yes\n");
				else printf("No\n");
			}
	}
	else dfs(lc,l,mid,L),L.clear(),dfs(rc,mid+1,r,R),R.clear();
	now.clear();
	for(int i = t;i > tmp;-- i) siz[s[i][1]] -= siz[s[i][0]],f[s[i][0]] = s[i][0];
	t = tmp;
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); m = Read();
	for(int i = 1;i <= n;++ i) f[i] = i,siz[i] = 1;
	for(int i = 1,u,v;i <= m;++ i)
	{
		scanf("%s",opt); u = Read(),v = Read();
		if(u > v) swap(u,v);
		if(opt[0] == 'Q') rt.push_back(node(u,v,i,i,1));
		else if(opt[0] == 'C') ma[make_pair(u,v)] = i;
		else rt.push_back(node(u,v,ma[make_pair(u,v)],i-1,0)),ma[make_pair(u,v)] = 0;
	}
	for(map<pair<int,int>,int>::iterator it = ma.begin();it != ma.end();++ it)//记得剩下的边要加进去 
		if(it->second)
			rt.push_back(node((it->first).first,(it->first).second,it->second,m,0));
	dfs(1,1,m,rt);
	return 0;
}

后记

一道题学懂线段树分治和带撤销并查集两个东西,血赚。

标签:勘测,return,int,siz,线段,查集,洞穴,ma,SDOI2008
来源: https://www.cnblogs.com/PPLPPL/p/15151029.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有