[cf319E]Ping-Pong
分析条件,不难发现仅有以下两类边:
1.若两区间严格相交(有公共段)且不相互包含,则两区间之间有双向边
2.若两区间相互包含,则小区间向大区间有单向边
对于第1类边,由于区间长度严格递增,可以通过线段树+并查集维护
具体的,1操作时在(线段树)区间$(l,r)$上加入该点,并与在$l$和$r$处已加入的点连边
可以使用vector暴力维护加入的点,并在连边后仅保留其中第一个点,显然可以均摊
引理:若$a$可以到$b$,则存在一条$a$到$b$的路径,使得(至多)仅有第1步用了第2类边
根据移动的条件,显然可以(跳过小区间)直接移动到大区间
结合引理,即要求$a$本身或某个包含$a$的区间与$b$在同一个并查集中
前者可以直接判定,(在前者不成立时)后者有以下结论——
结论:其成立当且仅当$a$被$b$所在并查集中所有区间的并包含
必要性显然,考虑充分性——
取$b$所在并查集中包含$a$左端点向右单位长度段的区间,显然这样的区间存在
若该区间包含$a$即得证,否则显然$a$不包含该区间,进而两者有双向边,与前者不成立矛盾
总复杂度为$o(n\log V)$,可以通过
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 100005 4 #define mid (l+r>>1) 5 pair<int,int>a[N]; 6 vector<int>v[N*100]; 7 int n,m,V,rt,p,l,r,fa[N],L[N],R[N],ls[N*100],rs[N*100]; 8 int find(int k){ 9 if (k==fa[k])return k; 10 return fa[k]=find(fa[k]); 11 } 12 void merge(int x,int y){ 13 x=find(x),y=find(y); 14 if (x==y)return; 15 fa[y]=x,L[x]=min(L[x],L[y]),R[x]=max(R[x],R[y]); 16 } 17 void update(int &k,int l,int r,int x,int y,int z){ 18 if ((l>y)||(x>r))return; 19 if (!k)k=++V; 20 if ((x<=l)&&(r<=y)){ 21 v[k].push_back(z); 22 return; 23 } 24 update(ls[k],l,mid,x,y,z); 25 update(rs[k],mid+1,r,x,y,z); 26 } 27 void add(int k,int l,int r,int x,int y){ 28 if (!k)return; 29 if (!v[k].empty()){ 30 for(int i=0;i<v[k].size();i++)merge(y,v[k][i]); 31 v[k].resize(1); 32 } 33 if (l==r)return; 34 if (x<=mid)add(ls[k],l,mid,x,y); 35 else add(rs[k],mid+1,r,x,y); 36 } 37 int main(){ 38 scanf("%d",&n); 39 for(int i=1;i<=n;i++){ 40 scanf("%d%d%d",&p,&l,&r); 41 if (p==1){ 42 a[++m]=make_pair(l,r); 43 fa[m]=m,L[m]=l,R[m]=r; 44 if (r-l>=2)update(rt,-1e9,1e9,l+1,r-1,m); 45 add(rt,-1e9,1e9,l,m),add(rt,-1e9,1e9,r,m); 46 } 47 if (p==2){ 48 r=find(r); 49 if ((find(l)==r)||(L[r]<=a[l].first)&&(a[l].second<=R[r]))printf("YES\n"); 50 else printf("NO\n"); 51 } 52 } 53 return 0; 54 }