SP9577

前言

可能大多数人看到这题就一眼 LCT 秒了。

但是我发现自己已经不会敲 LCT 了……

于是,提供一种离线做法。

思路

线段树分治。

考虑到每条边都有一个寿命,即从某时刻到某时刻里存在,我们具备了线段树分治的一个先决条件。特别的,如果最后都没有死,我们最后杀掉它。

我们维护一个时光序列,即某时刻中有的边,那么对每条边,我们在时光序列的某段中同时插入,最后对时光序列每个询问点进行查询即可,使用并查集复杂度是 \(O(nm\alpha(n))\) 的。

这不就暴力吗。

考虑到时光序列可以形如一颗 Leafy Tree (把实际信息存在叶子节点的树)的,我们用线段树维护它,查询时 dfs 一遍线段树即可。

什么,你问我 dfs 时怎么统计贡献?

使用带撤销并查集即可。

什么,你不会带撤销并查集?

复杂度 \(O(n+m\log m\log n)\)

Code

带撤销并查集使用按秩合并没有精神,以下代码利用 Treap 的思想,封装了一个 Heap_DSU

// Problem: SP9577 DYNACON1 - Dynamic Tree Connectivity
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/SP9577
// Memory Limit: 1.46 MB
// Time Limit: 395 ms

#include <algorithm>
#include <map>
#include <stdio.h>
#include <vector>
typedef long long llt;
typedef unsigned uint;typedef unsigned long long ullt;
typedef bool bol;typedef char chr;typedef void voi;
typedef double dbl;
template<typename T>bol _max(T&a,T b){return(a<b)?a=b,true:false;}
template<typename T>bol _min(T&a,T b){return(b<a)?a=b,true:false;}
template<typename T>T power(T base,T index,T mod){return((index<=1)?(index?base:1):(power(base*base%mod,index>>1,mod)*power(base,index&1,mod)))%mod;}
template<typename T>T lowbit(T n){return n&-n;}
template<typename T>T gcd(T a,T b){return b?gcd(b,a%b):a;}
template<typename T>T lcm(T a,T b){return(a!=0||b!=0)?a/gcd(a,b)*b:(T)0;}
template<typename T>T exgcd(T a,T b,T&x,T&y){if(!b)return y=0,x=1,a;T ans=exgcd(b,a%b,y,x);y-=a/b*x;return ans;}
template<typename T,typename rng>
class Heap_DSU //人类智慧势能并查集
{
	private:
		std::vector<T>Fath,Used,H;
	public:
		Heap_DSU(){Fath.clear(),Used.clear(),H.clear();}
		voi bzr(T n)
		{
			Fath.clear(),Used.clear(),H.clear();
			for(T i=0;i<n;i++)Fath.push_back(i),H.push_back(rng()());
		}
		T Time(){return Used.size();}
		T find(T p){return Fath[p]==p?p:find(Fath[p]);}
		bol connected(T a,T b){return find(a)==find(b);}
		bol merge(T a,T b)
		{
			a=find(a),b=find(b);
			if(a==b)return Used.push_back(a),false;
			if(H[a]<H[b])std::swap(a,b);
			Fath[b]=a,Used.push_back(b);
			return true;
		}
		bol revoke()
		{
			if(Used.empty())return false;
			T p=Used.back();Used.pop_back();
			if(p==Fath[p])return false;
			Fath[p]=p;return true;
		}
};
typedef std::pair<uint,uint>Pair;
struct Seg
{
	Seg*L,*R;uint len;std::vector<Pair>Way;
	voi build(uint n){L=R=NULL,Way.clear();if((len=n)>1)L=new Seg,R=new Seg,L->build(len>>1),R->build(len-(len>>1));}
	voi insert(uint l,uint r,Pair p)
	{
		if(l>=r)return;
		if(!l&&r==len){Way.push_back(p);return;}
		if(l<(len>>1))
		{
			if(r<=(len>>1))L->insert(l,r,p);
			else L->insert(l,len>>1,p),R->insert(0,r-(len>>1),p);
		}
		else R->insert(l-(len>>1),r-(len>>1),p);
	}
};
uint a=1919810;const uint b=10007,c=114513;
class rng{public:uint operator()(){return a=a*b+c;}};
Seg S;Heap_DSU<uint,rng>U;uint cnt;
bol Q[100005],Ans[100005];
uint A[100005],B[100005];
voi dfs(Seg*S)
{
	uint t=0;for(auto p:S->Way){t++,U.merge(p.first,p.second);}
	if(S->len==1)
	{
		if(Q[cnt])Ans[cnt]=U.connected(A[cnt],B[cnt]);
		cnt++;
	}
	else dfs(S->L),dfs(S->R);
	while(t--)U.revoke();
}
std::map<Pair,uint>M;
voi turn(uint&a,uint&b){if(a>b)std::swap(a,b);}
int main()
{
	chr op[5];uint n,m,u,v;scanf("%u%u",&n,&m),U.bzr(n),S.build(m);
	for(uint t=0;t<m;t++)
	{
		scanf("%s%u%u",op,&u,&v),turn(--u,--v);
		switch(*op)
		{
			case'a':M[Pair(u,v)]=t;break;
			case'r':S.insert(M[Pair(u,v)],t,Pair(u,v)),M.erase(Pair(u,v));break;
			case'c':Q[t]=true,A[t]=u,B[t]=v;
		}
	}
	for(auto s:M)S.insert(s.second,m,s.first);
	dfs(&S);
	for(uint t=0;t<m;t++)if(Q[t])puts(Ans[t]?"YES":"NO");
	return 0;
}
posted @ 2022-02-14 06:53  myee  阅读(46)  评论(0编辑  收藏  举报