#Loj121 动态图连通性

Description

你要维护一张无向简单图。你被要求加入删除一条边及查询两个点是否连通。
0:加入一条边。保证它不存在。
1:删除一条边。保证它存在。
2:查询两个点是否联通。

Input

输入的第一行是两个数 N,M。N≤5000,M≤500000。
接下来 M行,每一行三个数 op x y。op表示操作编号。

Output

对于每一个 op=2的询问,输出一行 Y 或 N ,表示两个节点是否连通。

Sample Input

200 5

2 123 127

0 123 127

2 123 127

1 127 123

2 123 127

Sample Output

N

Y

N

Hint

样例输入2
4 10
0 1 2
0 2 3
0 3 1
2 1 4
0 4 3
2 1 4
1 2 3
2 1 4
1 1 3
2 1 4
样例输出2
N
Y
Y
N

图的连通性= =容易想到用并查集来维护,然而并查集不支持删除操作啊o((⊙﹏⊙))o

这里介绍一个套路——线段树分治。

首先,这是一个离线算法,是以时间进行的分治,一般用来搞添加(删除)容易但删除(添加)困难的题目。

然后,这里仅介绍一点点其应用,因为代码量略大敲着太痛苦(然而它号称只要不在线,什么都能可持久),dalao可以挑战。

嗯,我们就用线段树分治来搞这道题,再讲一下分治的模型:

1、根据时间给节点打标记

2、跑一遍线段树

感觉好像什么都没说233

#include<bits/stdc++.h>
using namespace std;
#define Inc(i,L,R) for(register int i=(L);i<=(R);++i)
#define Red(j,R,L) for(register int j=(R);j>=(L);--j)
const int N = 5010,M = 5e5+10;
struct Data{
	int x,y;
};
Data e[M],q[M];
int cnte,cntq;
struct Union{
	int p[N],siz[N];
	stack<Data>stk;//我们开一个栈来保存合并了哪些点 
	inline void Reset(int n){
		Inc(i,1,n)p[i]=i;
		Inc(i,1,n)siz[i]=1;
	}
	inline int Findfa(int x){
		while(x!=p[x])x=p[x];
		return x;
	}
	inline void Merge(int idx){
		int x=e[idx].x,y=e[idx].y;
		int fx=Findfa(x),fy=Findfa(y);
		if(fx==fy)return ;
		if(siz[fx]>siz[fy])swap(fx,fy);
		stk.push((Data){fx,fy});
		siz[p[fx]=fy]+=siz[fx];
	}
	inline void Delete(int Presiz){//撤销操作 
		while(stk.size()>Presiz){
			Data tmp=stk.top();stk.pop();
			siz[tmp.y]-=siz[p[tmp.x]=tmp.x];
		}
	}
	inline bool Connect(int idx){
		return Findfa(q[idx].x)==Findfa(q[idx].y);
	}
}u;
struct SegMent{
	struct Tree{
		bool Loc;
		int L,r,idx;
		vector<int>vec;//存储边的编号 
	}t[M<<2];
	#define Ls v<<1
	#define rs v<<1|1
	inline void build(int v,int L,int r){
		t[v]=(Tree){0,L,r,0};
		t[v].vec.clear();
		if(L==r)return ;
		int Mid=L+r>>1;
		build(Ls,L,Mid),build(rs,Mid+1,r);
	}
	inline void update(int v,int A,int b,int idx){//每条边在哪些时刻出现过 
		if(t[v].L>b||t[v].r<A)return ;
		if(A<=t[v].L&&t[v].r<=b)return t[v].vec.push_back(idx),void();
		update(Ls,A,b,idx),update(rs,A,b,idx);
	}
	inline void LocQ(int v,int Time,int idx){
		if(t[v].L>Time||t[v].r<Time)return ;
		t[v].Loc=1;//小加速?不用完全遍历整个区间 
		if(t[v].L==t[v].r)return t[v].idx=idx,void();//问题的编号 
		LocQ(Ls,Time,idx),LocQ(rs,Time,idx);
	}
	inline void Stat(int v){
		if(!t[v].Loc)return ;
		int tmpsiz=u.stk.size();
		if(t[v].vec.size())Inc(i,0,t[v].vec.size()-1)u.Merge(t[v].vec[i]);
		if(t[v].L==t[v].r)return u.Connect(t[v].idx)?puts("Y"):puts("N"),void();
		Stat(Ls),Stat(rs);
		u.Delete(tmpsiz);
	}
}t;
int n,m,pre[M];
#define pr pair
#define mkpr make_pair
map<pair<int,int>,int>op;
inline void init(){
	scanf("%d%d",&n,&m);
	t.build(1,1,m);//以时间分治
	u.Reset(n);
	Inc(i,1,m){
		int opt,x,y;scanf("%d%d%d",&opt,&x,&y);
		if(x>y)swap(x,y);//看样例 
		if(opt==0){
			e[op[mkpr(x,y)]=++cnte]=(Data){x,y};
			pre[cnte]=i;
		}else if(opt==1){//删除代表这条边只在之前的某个时刻到现在i(i-1)时间存在 
			int idx=op[mkpr(x,y)];
			t.update(1,pre[idx],i,idx);//-1无所谓 
			pre[idx]=-1;
		}else {
			q[++cntq]=(Data){x,y};
			t.LocQ(1,i,cntq);
		}
	}
	Inc(idx,1,m)if(~pre[idx])t.update(1,pre[idx],m,idx);
}
int main(){
	init();
	t.Stat(1);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/dancingz/article/details/81086411