析合树

Posted ljzalc1022

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了析合树相关的知识,希望对你有一定的参考价值。

析合树

https://www.cnblogs.com/Paul-Guderian/p/11020708.html

定义

对于一个排列,称其中一个区间([l,r])为连续段若(r-l=max{[l,r]}-min{[l,r]}),即其中元素排序后权值形成一段连续的区间.

对于两个相交的连续段,发现它们的并是连续段,它们的交也是连续段.

所以我们可以找出(O(n))个本源连续段,满足其他所有连续段和本源连续段只有包含关系,且所有连续段都可以被若干本源连续段的并表示.

这样一来,本源连续段的包含关系就形成了一个树的结构,称其为析合树.

其中每个节点是析点或合点,满足

  • 叶子是析点
  • 析点的所有非平凡儿子区间(大小不等于1,也不是全集)都不是连续段
  • 合点的所有非平凡儿子区间都是连续段

如此一来,所有连续段要么是树上的节点,要么是合点的非平凡儿子区间.

许多关于连续段的问题都可以在析合树上解决.

构造

考虑增量构造,从小到大枚举(r),并处理所有右端点为(r)的节点.

考虑维护一个栈,按栈顶向下从右到左的顺序储存所有还没有父亲的节点.

定义当前节点为(now),初始(now=[r,r]).并对栈顶元素重复以下判断.

  • 若栈顶元素是合点,则判断(now)是否能成为它的最后一个儿子(对于合点在建立时纪录它第二个儿子的左端点),若能,则令它为(now),右端点变为(r),弹出栈顶
  • 否则,若(now)和栈顶元素可以构造一个连续段,建立一个新的合点作为它们的父亲,令这个新建的点为(now),弹出栈顶
  • 否则,看是否可以找出栈顶向下的一段连续元素,使得它们共同构成了一个连续段,建立一个新的析点作为他们的父亲,令这个新建的点为(now),弹出这些元素.若不能则退出

分析复杂度,前两种情况都是(O(1))的,第3种情况假如成功,那么复杂度均摊是(O(n))的,但是若失败可能会退化为(O(n^2))

所以可以先求出(L_i)表示以(i)为右端点的最大的连续段的左端点,可以解(L_i)直接判断是否可以成功找到这样的连续段.

那么如何求(L_i)呢,我们可以在(r)增大时对每个(l)维护(max{[l,r]}-min{[l,r]}-(r-l)),则最小值即=0的位置即是连续段的左端点,找到最左边的0就是(L_i)了.(max,min)的部分用单调栈维护即可.

建树部分复杂度(O(n)),求(L_i)复杂度(O(nlog n)),总时间复杂度(O(n log n))

Code

https://www.luogu.com.cn/problem/P4747

求包含([l,r])的最小连续段.

找到([l,l],[r,r])在析合树上的lca,若其为析点,那么答案为lca,若其为合点,那么答案是它们对应儿子构成的区间.

#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <vector>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
using namespace std;
inline char gc() {
//	return getchar();
	static char buf[100000],*l=buf,*r=buf;
	return l==r&&(r=(l=buf)+fread(buf,1,100000,stdin),l==r)?EOF:*l++;
}
template<class T> void rd(T &x) {
	x=0; int f=1,ch=gc();
	while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=gc();}
	while(ch>=‘0‘&&ch<=‘9‘){x=x*10-‘0‘+ch;ch=gc();}
	x*=f;
} 
const int maxn=100000+50;
int n,m,a[maxn];
namespace rmq {
	int bit[25],lg2[maxn];
	int mn[25][maxn],mx[25][maxn];
	void init() {
		bit[0]=1;
		for(int i=1;i<25;++i) bit[i]=bit[i-1]<<1;
		lg2[0]=-1;
		for(int i=1;i<=n;++i) lg2[i]=lg2[i>>1]+1;
		for(int i=1;i<=n;++i) mn[0][i]=mx[0][i]=a[i];
		for(int k=1;bit[k]<=n;++k) {
			for(int i=1;i+bit[k]-1<=n;++i) {
				mn[k][i]=min(mn[k-1][i],mn[k-1][i+bit[k-1]]);
				mx[k][i]=max(mx[k-1][i],mx[k-1][i+bit[k-1]]);
			}
		}
	}
	inline int Qmax(int l,int r) {
		int k=lg2[r-l+1];
		return max(mx[k][l],mx[k][r-bit[k]+1]);
	}
	inline int Qmin(int l,int r) {
		int k=lg2[r-l+1];
		return min(mn[k][l],mn[k][r-bit[k]+1]);
	}
}
namespace seg {
	const int maxnode=maxn<<2;
	int mn[maxnode],tag[maxnode];
	inline void change(int u,int d) {
		mn[u]+=d,tag[u]+=d;
	}
	inline void pushdown(int u) {
		if(tag[u]) {
			change(u<<1,tag[u]);
			change(u<<1|1,tag[u]);
			tag[u]=0;
		}
	}
	inline void pushup(int u) {
		mn[u]=min(mn[u<<1],mn[u<<1|1]);
	}
	void build(int u,int l,int r) {
		tag[u]=0;
		if(l==r) {
			mn[u]=0;
			return;
		}
		int mid=(l+r)>>1;
		build(lson);
		build(rson);
		pushup(u);
	}
	void update(int u,int l,int r,int ql,int qr,int qv) {
		if(l==ql&&r==qr) {
			change(u,qv);
			return;
		}
		int mid=(l+r)>>1;
		pushdown(u);
		if(qr<=mid) update(lson,ql,qr,qv);
		else if(ql>mid) update(rson,ql,qr,qv);
		else {
			update(lson,ql,mid,qv);
			update(rson,mid+1,qr,qv);
		}
		pushup(u);
	}
	int query(int u,int l,int r) {
		if(l==r) return l;
		int mid=(l+r)>>1;
		pushdown(u);
		if(mn[u<<1]==0) return query(lson);
		else return query(rson);
	} 
}
namespace dct {
	const int maxnode=maxn<<1;
	int ncnt;
	int root;
	int rnk[maxn];
	int L[maxnode],R[maxnode],M[maxnode],typ[maxnode];
	int head[maxnode];
	int dep[maxnode],parent[maxnode][20];
	struct edge {
		int to,nex;
		edge(int to=0,int nex=0):to(to),nex(nex){}
	};
	vector<edge> G;
	inline void addedge(int u,int v) {
		G.push_back(edge(v,head[u])),head[u]=G.size()-1;
	}
	void dfs(int u) {
		for(int i=1;i<20;++i) parent[u][i]=parent[parent[u][i-1]][i-1];
		for(int i=head[u];~i;i=G[i].nex) {
			int v=G[i].to;
			dep[v]=dep[u]+1;
			parent[v][0]=u;
			dfs(v);
		}
	}
	int jump(int u,int k) {
		for(int i=19;~i;--i) if(k>>i&1) {
			u=parent[u][i];
		}
		return u;
	}
	int lca(int u,int v) {
		if(dep[u]>dep[v]) swap(u,v);
		v=jump(v,dep[v]-dep[u]);
		if(u==v) return u;
		for(int i=19;~i;--i) {
			if(parent[u][i]!=parent[v][i]) {
				u=parent[u][i];
				v=parent[v][i];
			}
		}
		return parent[u][0];
	}
 	inline bool judge(int l,int r) {return r-l==rmq::Qmax(l,r)-rmq::Qmin(l,r);}
	void build() {
		static int sta[maxnode],sta1[maxn],sta2[maxn]; int top=0,top1=0,top2=0;
		seg::build(1,1,n);
		memset(head,-1,sizeof(head));
		for(int r=1;r<=n;++r) {
			while(top1&&a[sta1[top1]]<a[r]) {
				seg::update(1,1,n,sta1[top1-1]+1,sta1[top1],-a[sta1[top1]]);
				--top1;
			}
			sta1[++top1]=r;
			seg::update(1,1,n,sta1[top1-1]+1,sta1[top1],a[sta1[top1]]);
			while(top2&&a[sta2[top2]]>a[r]) {
				seg::update(1,1,n,sta2[top2-1]+1,sta2[top2],a[sta2[top2]]);
				--top2;
			}
			sta2[++top2]=r;
			seg::update(1,1,n,sta2[top2-1]+1,sta2[top2],-a[sta2[top2]]);
			int u=++ncnt; L[u]=R[u]=r; rnk[r]=u;
			int mnl=seg::query(1,1,n);
			while(top&&L[sta[top]]>=mnl) {
				if(typ[sta[top]]&&judge(M[sta[top]],r)) {
					addedge(sta[top],u);
					R[sta[top]]=r;
					u=sta[top--];
				}
				else if(judge(L[sta[top]],r)) {
					int v=++ncnt; typ[v]=1;
					L[v]=L[sta[top]],R[v]=r,M[v]=L[u];
					addedge(v,sta[top--]),addedge(v,u);
					u=v;
				}
				else {
					int v=++ncnt; addedge(v,u);
					do addedge(v,sta[top--]); while(!judge(L[sta[top]],r));
					L[v]=L[sta[top]],R[v]=r;
					addedge(v,sta[top--]);
					u=v;
				}
			}
			sta[++top]=u;
			seg::update(1,1,n,1,r,-1);
		}
		root=sta[top];
		dfs(root);
	}
	inline void sol(int l,int r) {
		l=rnk[l],r=rnk[r]; 
		int w=lca(l,r); 
		if(!typ[w]) printf("%d %d
",L[w],R[w]);
		else {
			l=jump(l,dep[l]-dep[w]-1);
			r=jump(r,dep[r]-dep[w]-1);
			printf("%d %d
",L[l],R[r]);
		}
	}
}
int main() {
//	freopen("1.in","r",stdin);
//	freopen("1.out","w",stdout);
	rd(n);
	for(int i=1;i<=n;++i) rd(a[i]);
	rmq::init();
	dct::build();
	rd(m);
	for(int i=1;i<=m;++i) {
		int x,y; rd(x),rd(y);
		dct::sol(x,y);
	}
	return 0;
}

以上是关于析合树的主要内容,如果未能解决你的问题,请参考以下文章

寒假要学习的知识点

P4747 [CERC2017]Intrinsic Interval

挑战计算48种依次泛化的假设情况下,总共有多少种不可再简化的析合范式?

微信小程序代码片段

VSCode自定义代码片段——CSS选择器

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js