XV.CF319E Ping-Pong

好题。

首先,离线下来离散化显然是不用说的。

然后观察这里“可以移动”的定义,发现明显可以类比图论中的连边。发现边只有有向边(两区间包含)和无向边(两区间相交)两种,又因为我们只管连通性,所以无向边可以直接使用并查集维护。而包含关系又具有可传递性,故我们最终会发现必定存在一条路径使得最多经过一条有向边(经过多条有向边的路径可以被合并)。于是我们如果使用并查集维护的话,则只需要判断所有互相可达的小区间合并后,询问的两个区间是否相同或者后者包含前者即可。

然后就是这题的精髓所在了——

我们将每个区间在线段树中拆成\(\log n\)个区间,使用vector存在节点上。则对于线段树中的某个叶子节点,它的所有父亲节点上的区间中,包含了所有包含当前叶节点的区间。

于是我们在插入一个区间后,它左右两端所在的节点的所有祖先节点上的区间都与它有交;而因为题目保证插入的区间长度递增,所以这必定连的都是无向边,故我们直接使用并查集合并即可。合并之后,在每个节点的vector内,只需保留当前区间即可。

在合并完之后,就直接拆区间即可。判断就依靠我们上文提到的性质判断。

时间复杂度\(O\Big(n\log n\alpha(n)\Big)\)

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,cnt;
struct oper{
	int tp,l,r;
}q[100100];
vector<int>v[800100],dis;
int L[100100],R[100100],dsu[100100];
int find(int x){return dsu[x]==x?x:dsu[x]=find(dsu[x]);}
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
void merge(int x,int l,int r,int P,int id){
	if(l>P||r<P)return;
	if(!v[x].empty()){
		for(auto ip:v[x])ip=find(ip),dsu[ip]=id,L[id]=min(L[id],L[ip]),R[id]=max(R[id],R[ip]);
		v[x].clear(),v[x].push_back(id);
	}
	if(l!=r)merge(lson,l,mid,P,id),merge(rson,mid+1,r,P,id);
}
void assign(int x,int l,int r,int id){
	if(r<=L[id]||l>=R[id])return;
	if(L[id]<l&&r<R[id]){v[x].push_back(id);return;}
	assign(lson,l,mid,id),assign(rson,mid+1,r,id);
}
int main(){
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&q[i].tp,&q[i].l,&q[i].r);
		if(q[i].tp==1)dis.push_back(q[i].l),dis.push_back(q[i].r);
	}
	sort(dis.begin(),dis.end()),dis.resize(n=unique(dis.begin(),dis.end())-dis.begin());
	for(int i=1;i<=m;i++){
		if(q[i].tp==1){
			cnt++;
			dsu[cnt]=cnt,q[i].l=lower_bound(dis.begin(),dis.end(),q[i].l)-dis.begin()+1,q[i].r=lower_bound(dis.begin(),dis.end(),q[i].r)-dis.begin()+1;
			L[cnt]=q[i].l,R[cnt]=q[i].r;
			merge(1,1,n,q[i].l,cnt),merge(1,1,n,q[i].r,cnt);
			assign(1,1,n,cnt);
		}else{
			int x=q[i].l,y=q[i].r;
			x=find(x),y=find(y);
			puts(x==y||(L[y]<L[x]&&L[x]<R[y])||(L[y]<R[x]&&R[x]<R[y])?"YES":"NO");
		}
	}
	return 0;
}