首页 > 其他分享 >析合树

析合树

时间:2023-12-30 20:12:44浏览次数:22  
标签:cnt 合点 cur int vl maxn 析合树

\(\color{black}{\texttt N}\color{red}{\texttt{ityacke}}\) 瑞萍:废(三声)物。

定义连续段为区间 \([l,r]\),其中 \([l,r]\) 排序后值域连续。

定义本原连续段为任意连续段与其无交或包含的连续段。把所有本原连续段依包含/分割组织成的树,叫析合树(显然此成立)。

析合树有两类点,析点和合点。合点满足:按序列下标区间排序其儿子,其儿子的值域区间递增或递减。否则是析点。

我们认为叶节点是析点。

合点性质:任意儿子子区间(按序列下标排序)都构成连续段。

析点性质:任意长度大于一的儿子子区间(按序列下标排序)都不构成连续段。

证明是 trival 的。

构建。我们使用 \(O(n\log n)\) 的增量法构建,具体而言:

维护一个栈,代表目前的析合树森林的根;最后一定只会剩下一个元素。

当一个点被加入时,不断找到前面第一个能与其组成连续段的点(保证本原性)组成一个连续段,这样的过程是(接下来称找到的点为那个点):

  1. 发现这个点一定是栈中某个点的下标最左点。

  2. 如果前面这个点相邻,且是合点,就把当前点挂到那个点下面去,并且置当前点为那个点。

  3. 否则把那个点到栈顶和当前点的所有点拉一个新父亲(如果那个点是栈顶就是合点,否则是析点),并置当前点为这个新父亲。

  4. 重复这样的过程直到不能进行。

找到第一个前面的点的过程用扫描线+线段树+单调栈不难转为区间 \(\min\) 及其位置、区间加维护,线段树二分即可。

P4747

询问时找到两位置的 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;
}

标签:cnt,合点,cur,int,vl,maxn,析合树
From: https://www.cnblogs.com/british-union/p/17936732/xiheshu

相关文章

  • 关于区间连续段问题 (析合树)
    有部分题目需要处理关于区间连续段的问题(一般来说,对于一个排列,如果一个区间的值连读,就为一个连续段。)区间连续段看似不太好维护,其实有一种处理它的利器:析合树。(也可能只是析合树的思想),就能方便的维护这一个东西。析合树其实这个名字不重要......
  • 析合树学习笔记
    前情回顾。因为学了PQ-Tree而zhy提起析合树与PQ-Tree类似的结构关系于是就去又看了下析合树。这个算法太有用了!至少比PQ-Tree有用多了!析合树是处理排列连续段问题的利器。其实还是没用对于一个排列的子区间,如果它的值域也是一段长度相同的区间的话就称为它是该排列的连......
  • 【专题】析合树计数
    【专题】析合树计数djwj233析合树学习笔记rzO_KQP_Orz析合树形态计数dp析合树子段:连续子序列非平凡子段:长度\(\in[2,n-1]\)的子段连续段:排序后值域连续的子段令\(I_U\)表示\(U\)的所有连续段。本原连续段:若没有其他\(I_U\)内的连续段与当前连续段相交,......