好题。
首先,离线下来离散化显然是不用说的。
然后观察这里“可以移动”的定义,发现明显可以类比图论中的连边。发现边只有有向边(两区间包含)和无向边(两区间相交)两种,又因为我们只管连通性,所以无向边可以直接使用并查集维护。而包含关系又具有可传递性,故我们最终会发现必定存在一条路径使得最多经过一条有向边(经过多条有向边的路径可以被合并)。于是我们如果使用并查集维护的话,则只需要判断所有互相可达的小区间合并后,询问的两个区间是否相同或者后者包含前者即可。
然后就是这题的精髓所在了——
我们将每个区间在线段树中拆成\(\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;
}