析合树
\(\color{black}{\texttt N}\color{red}{\texttt{ityacke}}\) 瑞萍:废(三声)物。
定义连续段为区间 \([l,r]\),其中 \([l,r]\) 排序后值域连续。
定义本原连续段为任意连续段与其无交或包含的连续段。把所有本原连续段依包含/分割组织成的树,叫析合树(显然此成立)。
析合树有两类点,析点和合点。合点满足:按序列下标区间排序其儿子,其儿子的值域区间递增或递减。否则是析点。
我们认为叶节点是析点。
合点性质:任意儿子子区间(按序列下标排序)都构成连续段。
析点性质:任意长度大于一的儿子子区间(按序列下标排序)都不构成连续段。
证明是 trival 的。
构建。我们使用 \(O(n\log n)\) 的增量法构建,具体而言:
维护一个栈,代表目前的析合树森林的根;最后一定只会剩下一个元素。
当一个点被加入时,不断找到前面第一个能与其组成连续段的点(保证本原性)组成一个连续段,这样的过程是(接下来称找到的点为那个点):
-
发现这个点一定是栈中某个点的下标最左点。
-
如果前面这个点相邻,且是合点,就把当前点挂到那个点下面去,并且置当前点为那个点。
-
否则把那个点到栈顶和当前点的所有点拉一个新父亲(如果那个点是栈顶就是合点,否则是析点),并置当前点为这个新父亲。
-
重复这样的过程直到不能进行。
找到第一个前面的点的过程用扫描线+线段树+单调栈不难转为区间 \(\min\) 及其位置、区间加维护,线段树二分即可。
询问时找到两位置的 LCA,如果 LCA 是析点答案就是 LCA,否则是两个点到 LCA 经过的最后一个点(作为左右端点)。
值得一提的是,本题存在离线的不使用析合树的做法。
在模板数据结构里面算码量比较大的了把。
// Problem: P4747 [CERC2017] Intrinsic Interval
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P4747
// Memory Limit: 500 MB
// Time Limit: 3000 ms
// Author:British Union
// Long live UOB and koala
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
#define ls (k<<1)
#define rs (k<<1|1)
#define mid ((l+r)>>1)
int n,pos=0;
namespace ST{
int xds[maxn<<2],add[maxn<<2],xdp[maxn<<2];
void ADD(int k,int v){
add[k]+=v,xds[k]+=v;
}
void pushup(int k){
if(xds[ls]<xds[rs])xdp[k]=xdp[ls];
else xdp[k]=xdp[rs];
xds[k]=min(xds[ls],xds[rs]);
}
void pushdown(int k){
ADD(ls,add[k]);ADD(rs,add[k]);
add[k]=0;
}
void buildt(int k,int l,int r){
xdp[k]=r;
if(l==r)return ;
buildt(ls,l,mid);buildt(rs,mid+1,r);
}
void modify(int k,int l,int r,int x,int y,int v){
if(x>y)return ;
if(x<=l&&r<=y)return ADD(k,v);
pushdown(k);
if(x<=mid)modify(ls,l,mid,x,y,v);
if(mid<y)modify(rs,mid+1,r,x,y,v);
pushup(k);
}
void query(int k,int l,int r,int p){
if(xds[k]>0)return ;
if(l==r){
pos=max(pos,l);
return ;
}
pushdown(k);
if(p<=mid)query(ls,l,mid,p);
else{
if(xds[ls]==0)pos=max(pos,xdp[ls]);
query(rs,mid+1,r,p);
}
}
}
using namespace ST;
struct node{
int al,ar,vl,vr,id;
node(int a=0,int b=0,int c=0,int d=0,int e=0){
al=a,ar=b,vl=c,vr=d,id=e;
}
};
vector<int> e[maxn];
namespace DT{
node st[maxn],val[maxn];
int tp,fa[maxn],cnt=0,rt,P[maxn],rev[maxn];
int type[maxn];//0 正序合点 1 析点 2 倒序合点
void ins(int i){
bool f=1;
node cur(i,i,P[i],P[i],++cnt);
rev[i]=cnt;
val[cnt]=cur;
type[cnt]=1;
while(f){
if(!tp){f=0;break;}
node t=st[tp];
if((t.vr+1==cur.vl||t.vl-1==cur.vr)&&type[t.id]!=1){
if((type[t.id]==0&&t.vl<cur.vl)||(type[t.id]==2&&t.vl>cur.vl)){
fa[cur.id]=t.id;
t.ar=cur.ar,t.vl=min(t.vl,cur.vl),t.vr=max(t.vr,cur.vr);
val[t.id]=t;--tp;cur=t;f=1;
continue;
}
}
pos=0;
query(1,1,n,cur.al-1);
if(pos==0){f=0;break;}
if(pos==t.al){
fa[t.id]=fa[cur.id]=++cnt;
node N(t.al,cur.ar,min(cur.vl,t.vl),max(cur.vr,t.vr),cnt);
if(t.vl<cur.vl)type[N.id]=0;
else type[N.id]=2;
val[cnt]=N;--tp;cur=N;f=1;
continue;
}
int Min=cur.vl,Max=cur.vr;
fa[cur.id]=++cnt;
node N(cur.al,cur.ar,0,0,cnt);
while(1){
t=st[tp];--tp;N.al=t.al;
Min=min(Min,t.vl);Max=max(Max,t.vr);
fa[t.id]=N.id;
if(t.al==pos)break;
}
N.vl=Min,N.vr=Max;
type[N.id]=1;val[N.id]=N;f=1;cur=N;
}
st[++tp]=cur;
}
int stmn[maxn],stmx[maxn],tp1=0,tp2=0;
void upd(int i){
modify(1,1,n,1,i-1,-1);
while(tp1&&P[stmn[tp1]]>P[i])modify(1,1,n,stmn[tp1-1]+1,stmn[tp1],P[stmn[tp1]]-P[i]),tp1--;
stmn[++tp1]=i;
while(tp2&&P[stmx[tp2]]<P[i])modify(1,1,n,stmx[tp2-1]+1,stmx[tp2],P[i]-P[stmx[tp2]]),tp2--;
stmx[++tp2]=i;
}
void build(){
for(int i=1;i<=n;i++)upd(i),ins(i);
rt=st[tp].id;
for(int i=1;i<=cnt;i++)e[fa[i]].push_back(i);
}
}
using namespace DT;
int dep[maxn],to[maxn],son[maxn],sze[maxn],seg[maxn],Rev[maxn];
void dfs1(int u){
dep[u]=dep[fa[u]]+1,sze[u]=1;
for(auto v:e[u]){
dfs1(v);
if(sze[v]>sze[son[u]])son[u]=v;
sze[u]+=sze[v];
}
}
void dfs2(int u,int t){
to[u]=t;seg[u]=++seg[0],Rev[seg[0]]=u;
if(son[u])dfs2(son[u],t);
for(auto v:e[u])if(v!=son[u])dfs2(v,v);
}
int LCA(int u,int v){
while(to[u]!=to[v]){
if(dep[to[u]]<dep[to[v]])swap(u,v);
u=fa[to[u]];
}
return dep[u]<dep[v]?u:v;
}
int Find(int u,int k){
while(dep[u]-dep[fa[to[u]]]<=k){
k-=dep[u]-dep[fa[to[u]]];
u=fa[to[u]];
}
return Rev[seg[u]-k];
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>P[i];
int q;cin>>q;
buildt(1,1,n);build();
dfs1(rt);dfs2(rt,rt);
for(int i=1;i<=q;i++){
int x,y;cin>>x>>y;
x=rev[x],y=rev[y];
int L=LCA(x,y);
if(type[L]==1){
cout<<val[L].al<<" "<<val[L].ar<<endl;
}else{
int lx=Find(x,dep[x]-dep[L]-1),ly=Find(y,dep[y]-dep[L]-1);
cout<<val[lx].al<<" "<<val[ly].ar<<endl;
}
}
return 0;
}
YJX AK IOI