析合树学习笔记

前情回顾。因为学了 PQ-Tree 而 zhy 提起析合树与 PQ-Tree 类似的结构关系于是就去又看了下析合树。

这个算法太有用了!至少比 PQ-Tree 有用多了!析合树是处理排列连续段问题的利器。其实还是没用

对于一个排列的子区间,如果它的值域也是一段长度相同的区间的话就称为它是该排列的连续段。记一个排列的连续段集合为 \(I_p\)。然而 \(|I_p|\)\(O(n^2)\) 级别的,不易于维护。

我们观察连续段的性质,发现它很类似于 PQ-Tree 的集合连续限制:对于两个非平凡相交的连续段 \(I_1,I_2\)(非平凡相交即 \(I_1\cap I_2\notin \{\emptyset,I_1,I_2\}\)),\(I_1\cup I_2\)\(I_p\) 中。

因此我们只需要考虑那些不跟其它段非平凡相交的连续段(称之为“本原段”),依据包含关系建出树,这就是析合树。

析合树上的每一个点 \(u\) 都代表了一个值域区间 \([pl_u,pr_u]\)\(u\) 的儿子按照连续段在排列中的出现位置排列,这些儿子对应的值域区间两两无交,且并起来恰好是 \([pl_u,pr_u]\)

那么如何用析合树刻画整个 \(I_p\) 呢?

注意到析合树的儿子不是按照值域顺序排的,所以我们对于一个点按照儿子的值域顺序离散话,得到了一个排列称为 \(P_u\)

析合树上的每个点都管辖了 \(I_p\) 中的一些连续段,我们断言析合树上只有两类点:

  • 合点,即满足 \(P_u\) 单调的点,此时 \(P_u\) 中所有长度大于 \(1\) 的区间所对应的儿子值域区间并起来的区间都是 \(u\) 所管辖的连续段。

  • 析点,即不是合点的点,此时只有 \([pl_u,pr_u]\)\(u\) 所管辖的连续段。

如何建立析合树?

我们对于一个前缀建出对应的析合森林,用栈从左往右保存所有的根,每次增量构建。

假设现在要增加 \(p_i\) 到末尾,我们先考虑如何判定 \([j,i]\) 是否为连续段。相当于检查 \(\max_{t=i}^j p_t-\min_{t=i}^j p_t=i-j\),当然可以直接 ST 表,但这里需要先写一颗线段树,支持每次找出最靠左的 \(j\) 满足 \([j,i]\) 的连续段。注意到 \(\max_{t=i}^j p_t-\min_{t=i}^j p_t-i+j\) 非负,用两个单调栈+线段树扫描 \(i\) 维护 \(j\) 即可。

接下来:

  • 我们先判断它能否成为栈顶结点的儿子,即判断栈顶是否是合点且当前插入的点是否与栈顶节点的最右儿子连续。如果能就插入成栈顶节点的儿子。
  • 如果不能成为栈顶的儿子,利用线段树求出来的最小的 \(j\) 判断是否能将栈顶的若干个连续的结点都合并成一个结点。如果能找出节点最少的一个满足连续的后缀,把它们合并,作为一个新建合点/析点的儿子(如果两个区间就可以合并了那么就需要新建一个合点,否则建析点)。
  • 如果不做上述两种操作,直接把当前结点压栈,退出,否则将栈顶节点作为当前节点继续插入。

时间复杂度 \(O(n\log n)\),存在 \(O(n)\) 做法,但由于需要 \(O(n)-O(1)\) RMQ 意义不大。

以下代码统计了排列连续段数。

#include <cstdio>
#include <algorithm>
using namespace std;
int read(){
	char c=getchar();int x=0;
	while(c<48||c>57) c=getchar();
	do x=(x<<1)+(x<<3)+(c^48),c=getchar();
	while(c>=48&&c<=57);
	return x;
}
const int N=300003;
typedef long long ll;
int n;
int p[N];
namespace dct{
	namespace sgt{
#define lc (p<<1)
#define rc (p<<1|1)
		int tg[N<<2],mn[N<<2];
		void proc(int p,int v){tg[p]+=v;mn[p]+=v;}
		void pushdown(int p){
			if(tg[p]){
				proc(lc,tg[p]);
				proc(rc,tg[p]);
				tg[p]=0;
			}
		}
		void update(int sl,int sr,int v,int p=1,int l=1,int r=n){
			if(sl>sr) return;	
			if(sl<=l&&r<=sr) return proc(p,v);
			pushdown(p);
			int mid=(l+r)>>1;
			if(sl<=mid) update(sl,sr,v,lc,l,mid);
			if(sr>mid) update(sl,sr,v,rc,mid+1,r);
			mn[p]=min(mn[lc],mn[rc]);
		}
		int query(int p=1,int l=1,int r=n){
			if(l==r) return l;
			pushdown(p);
			int mid=(l+r)>>1;
			if(!mn[lc]) return query(lc,l,mid);
			return query(rc,mid+1,r);
		}
		bool ask(int x,int p=1,int l=1,int r=n){
			if(l==r) return !mn[p];
			pushdown(p);
			int mid=(l+r)>>1;
			if(x<=mid) return ask(x,lc,l,mid);
			else return ask(x,rc,mid+1,r);
		}
		void show(int p=1,int l=1,int r=n){
			if(l==r){printf("%d ",mn[p]);return;}
			pushdown(p);
			int mid=(l+r)>>1;
			show(lc,l,mid);
			show(rc,mid+1,r);
		}
#undef lc
#undef rc
	}
	const int T=N<<1;
	bool dc[T];
	int pl[T],pr[T],rs[T],ft[T],sz[T],cnt;
	int smn[N],tmn;
	int smx[N],tmx;
	int stk[N],tp;
	void chmn(int &x,int v){if(x>v) x=v;}
	void chmx(int &x,int v){if(x<v) x=v;}
	void build(){
		cnt=n;
		for(int i=1;i<=n;++i){
			while(tmn&&p[smn[tmn]]>p[i]){
				sgt::update(smn[tmn-1]+1,smn[tmn],p[smn[tmn]]);
				--tmn;
			}
			while(tmx&&p[smx[tmx]]<p[i]){
				sgt::update(smx[tmx-1]+1,smx[tmx],-p[smx[tmx]]);
				--tmx;
			}
			sgt::update(smn[tmn]+1,i-1,-p[i]);
			sgt::update(smx[tmx]+1,i-1,p[i]);
			sgt::update(1,i-1,-1);
			smx[++tmx]=smn[++tmn]=i;
			// sgt::show(1);putchar('\n');
			int lim=sgt::query(),cur=i;
			dc[i]=0;pl[i]=pr[i]=i;
			while(true){
				if(!tp||pl[cur]<=lim){stk[++tp]=cur;break;}
				int x=stk[tp];
				if(dc[x]&&sgt::ask(pl[rs[x]])){
					ft[rs[x]=cur]=x;
					--tp;cur=x;
					continue;
				}
				if(sgt::ask(pl[x])){
					dc[++cnt]=1;
					pl[cnt]=pl[x];
					pr[cnt]=i;
					rs[cnt]=cur;
					ft[x]=ft[cur]=cnt;
					--tp;cur=cnt;
					continue;
				}
				dc[ft[cur]=++cnt]=0;pr[cnt]=i;rs[cnt]=cur;
				do{x=stk[tp--];ft[x]=cnt;pl[cnt]=pl[x];}
				while(!sgt::ask(pl[x]));
				cur=cnt;
			}
			// for(int i=1;i<=tp;++i) printf("%d ",stk[i]);
			// putchar('\n');
		}
		// for(int i=1;i<=cnt;++i) printf("[%d,%d] %d fath:%d\n",pl[i],pr[i],dc[i],ft[i]);
		ll res=n;
		for(int i=1;i<=cnt;++i) if(ft[i]) ++sz[ft[i]];
		for(int i=n+1;i<=cnt;++i)
			if(dc[i]) res+=(ll)sz[i]*(sz[i]-1)>>1;
			else ++res;
		printf("%lld\n",res);
	}
}
int main(){
	n=read();
	for(int i=1;i<=n;++i){
		int x=read(),y=read();
		p[x]=y;
	}
	// for(int i=1;i<=n;++i) printf("%d ",p[i]);
	// putchar('\n');
	dct::build();
	return 0;
}
posted @ 2023-08-23 09:01  yyyyxh  阅读(52)  评论(0编辑  收藏  举报