首页 > 其他分享 >析合树小记-

析合树小记-

时间:2024-04-03 15:16:02浏览次数:15  
标签:cnt top stk 连续 本原 析合树 now 小记

定义

  • 排列:由 \(1\sim n\) 打乱组成的序列。

  • 连续段:\([l,r]\) 被称为连续段,当且仅当排列 \(a\) 中 \(a_{l...r}\) 在排序后值域也连续。

构想

析合树是一种处理排列连续段问题的有力数据结构。

但是一个排列的连续段数可能达到 \(O(n^2)\),我们该如何存储?

一些连续段可能会与其他连续段严格相交,我们称这些连续段为非本原连续段

其他连续段称为本原连续段,简称本原段

我们可以证明,一个排列的本原段个数不超过 \(O(n)\),于是我们考虑只存储本原段。

一个本原段可能由多个本原段组成,所以我们采用树形结构存储。

每个结点表示一个本原段。若他不是叶子,那么他是由儿子对应的本原段从左到右连接而成的。

析合树

析合树是一种符合我们构想的一种树,他将上面的结点分为了两类:析点、合点。在这里,我们不讨论叶子的分类

  • 合点:一个结点是合点,当且仅当他的儿子本原段也是有序的,可以是从小到大,也可以是从大到小。例如:\([1,10]=[1,3]+[4,5]+[6,9]+[10,10]\)。

  • 析点:不是合点的点就是析点。例如:\([1,10]=[6,9]+[1,3]+[10,10]+[4,5]\)。

定义子连续段为一个点的连续几个儿子组成的非本原连续段。

  • 定理:析点的儿子无法组成子连续段。

  • 证明:反证法,假设有一段儿子组成了子连续段,那么不存在其他连续段与这个子连续段严格相交。根据定义,这些儿子会合并成一个合点,矛盾。

构建

这里使用增量法。

维护一个栈,存储一棵析合树森林

加入 \(a_i\),我们令 \(now\) 为新的结点,分情况:

  • 栈顶不是叶子,且 \(now\) 可以作为栈顶的儿子。

此时栈顶一定不是析点,否则会存在子连续段,所以一定是合点。

我们还需要判断接为栈顶儿子后栈顶是否仍然是合点,额外记录 \(M_u\) 表示点 \(u\) 最右边的儿子的左端点,判断 \([M_u,i]\) 是否为连续段即可。

最后把栈顶作为新的 \(now\)。

  • 与栈顶并列,形成一个合点,作为父亲。

判断两者是否形成一个连续段即可,然后把合点作为 \(now\)。

  • 与栈顶部若干个点一同并列,形成一个析点,作为父亲。

我们不断弹出栈顶,并判断是否已经形成一个连续段,若已经形成则退出。然后把析点作为 \(now\)。

每次还需要判断 \(now\) 是否还能与先前本原段合并,我们需要支持查找可以合并到的最早的位置,可以用 这题 的线段树完成。

这里放一下 CF 的图。

我们要用一个 ST 表、一个线段树、三个栈,时间复杂度 \(O(n\log n)\)。当然你愿意的话,可以试试学 \(O(n)\) 的析合树。

点击查看代码
#define ll long long
#define pb push_back
using namespace std;
const ll maxn=2e5+10;
ll n,a[maxn];
struct RMQ{
	ll st_min[20][maxn], st_max[20][maxn], Log[maxn];
	void build(){
		for(ll i=2;i<=n;i++) Log[i]=Log[i>>1]+1;
		for(ll i=1;i<=n;i++) st_min[0][i]=st_max[0][i]=a[i];
		for(ll i=1;(1<<i)<=n;i++)
			for(ll j=1;j+(1<<i)-1<=n;j++)
				st_min[i][j]=min(st_min[i-1][j],st_min[i-1][j+(1<<i-1)]),
				st_max[i][j]=max(st_max[i-1][j],st_max[i-1][j+(1<<i-1)]);
	}
	ll qry_min(ll l,ll r){
		ll k=Log[r-l];
		return min(st_min[k][l],st_min[k][r-(1<<k)+1]);
	}
	ll qry_max(ll l,ll r){
		ll k=Log[r-l];
		return max(st_max[k][l],st_max[k][r-(1<<k)+1]);
	}
}D;
struct SGT{
	ll tag[maxn<<2], mn[maxn<<2], pos[maxn<<2];
	void addtag(ll p,ll v){
		tag[p]+=v, mn[p]+=v;
	}
	void pushdown(ll p){
		addtag(p<<1,tag[p]), addtag(p<<1|1,tag[p]);
		tag[p]=0;
	}
	void modify(ll p,ll l,ll r,ll ql,ll qr,ll v){
		if(ql<=l&&r<=qr) {addtag(p,v); return;}
		pushdown(p); ll mid=l+r>>1;
		if(ql<=mid) modify(p<<1,l,mid,ql,qr,v);
		if(mid<qr) modify(p<<1|1,mid+1,r,ql,qr,v);
		mn[p]=min(mn[p<<1],mn[p<<1|1]);
		if(mn[p]==mn[p<<1]) pos[p]=pos[p<<1];
		else pos[p]=pos[p<<1|1];
	}
	void build(ll p,ll l,ll r){
		pos[p]=l;
		if(l==r) return;
		ll mid=l+r>>1;
		build(p<<1,l,mid), build(p<<1|1,mid+1,r);
	}
}T;
ll chk(ll l,ll r) {return D.qry_max(l,r)-D.qry_min(l,r)-(r-l)==0;}
ll stk_min[maxn],top_min,stk_max[maxn],top_max;
ll stk[maxn],top,typ[maxn<<1],L[maxn<<1],R[maxn<<1],M[maxn<<1],cnt,id[maxn];
vector<ll>to[maxn<<1];
void build(){
	D.build();
	T.build(1,1,n);
	for(ll i=1;i<=n;i++){
		while(top_min&&a[stk_min[top_min]]>a[i])
			T.modify(1,1,n,stk_min[top_min-1]+1,stk_min[top_min],a[stk_min[top_min]]), --top_min;
		while(top_max&&a[stk_max[top_max]]<a[i])
			T.modify(1,1,n,stk_max[top_max-1]+1,stk_max[top_max],-a[stk_max[top_max]]), --top_max;
		T.modify(1,1,n,stk_min[top_min]+1,i,-a[i]);
		T.modify(1,1,n,stk_max[top_max]+1,i,a[i]);
		stk_min[++top_min]=i, stk_max[++top_max]=i;
		id[i]=++cnt, L[cnt]=R[cnt]=i;
		ll now=cnt;
		while(top&&L[stk[top]]>=T.pos[1]){
			if(typ[stk[top]]&&chk(M[stk[top]],i)){
				to[stk[top]].pb(now);
				R[stk[top]]=i, M[stk[top]]=L[now], now=stk[top];
				--top;
			} else if(chk(L[stk[top]],i)){
				++cnt, typ[cnt]=1;
				L[cnt]=L[stk[top]], R[cnt]=i, M[cnt]=L[now];
				to[cnt].pb(stk[top]), to[cnt].pb(now);
				now=cnt, --top;
			} else{
				++cnt, to[cnt].pb(now);
				do to[cnt].pb(stk[top--]);
				while (top&&!chk(L[stk[top]],i));
				L[cnt]=L[stk[top]], R[cnt]=i, to[cnt].pb(stk[top]);
				now=cnt, --top;
			}
		} stk[++top]=now;
		T.modify(1,1,n,1,i,-1);
	}
}

标签:cnt,top,stk,连续,本原,析合树,now,小记
From: https://www.cnblogs.com/Sktn0089/p/18112712

相关文章

  • JVM的一些小记
    把最近的一些从jvm原理书中的一些摘要记一下JVM1.对象的内存布局是什么样子?对象在堆内存中的存储布局可以划分3个部分,对象头,实例数据,对齐填充。对象头包括两个部分,第一是对象的运行时数据,如对象哈希码,GC分代年龄,锁状态等,这部分称作MarkWord。第二是类型指针,java通过......
  • 欧拉五边形数定理小记
    欧拉五边形数定理(Pentagonalnumbertheorem)约定\[\phi(x)=\prod_{n=1}^{\infty}(1-x^n)\]描述\[\begin{aligned}\phi(x)&=\sum_{k=-\infty}^\infty(-1)^kx^{k(3k-1)/2}\\&=1+\sum_{k=1}^{\infty}(-1)^k(x^{k(3k-1)/2}+x^{k(3k+1)/2})\end{aligned}\]......
  • 数论小记
    做到就会补进来>w<\[d(ij)=\sum_{x|i}\sum_{y|j}[\gcd(x,y)=1]\]其中\(d\)是约数个数,证明如下:考虑\(x,y\)造就的\(xy=d\)的贡献,显然覆盖完全,那么我们现在需要一个\(d\)只能有一种产生贡献的方式。考虑一个质数\(p\)在\(x,y,d\)中的幂次分别为\(x',y',d'\),在\(......
  • Linux操作系统小记
    1.finalshell使用Linux终端打开-输入ifconfig-查看ip地址finalshell-----SSH链接----输入信息2.Linux常用命令ls-a/      根目录隐藏文件ls-l/       竖着显示ls-lh/      竖着显示,并且包含大小pwd        ......
  • $k$ 进制 FWT 小记
    上文:FWT快速沃尔什变换概念\(k\)进制的DWT本质上是二进制操作的一个扩展操作。原来的位运算也推广到了\(k\)进制上。\(\text{or}\)卷积定义两个数\(x_{1...k},y_{1...k}\)的\(k\)进制或运算定义为:\(z_i=\max(x_i,y_i)\)。换言之,在每个维度上取\(\max\)。分析......
  • conda 使用小记
    不知道为啥用了这么久conda了还总是能遇到各种问题,哎。下载:https://www.anaconda.com/download#downloadsconda因为使用sh脚本安装的,所以有没有管理员权限都很好装。在服务器上安装时,记得把目录改为用户目录~/然后要将环境变量添加到\(~/.bashrc\)中。可以让命令行默......
  • 启发式合并小记
    适用范围当题目中查询有关子树中的问题,而往往涉及类似莫队中每种值出现个数这类比较难用线段树快速维护的时候,我们可以考虑用启发式合并。过程启发式合并其实是优雅的暴力,具体思路就是:统计\(u\)子树的答案,我们先把\(u\)除了重儿子之外的所有儿子的答案统计了,然后再统计重儿......
  • 洛谷 P4173 残缺的字符串 卡常小记
    首先,使用匹配函数\(P(x_i,x_j)=x_ix_j-x_i^2[j\neq0]\)。容易发现,当存在\(i\neqj\)时,\(x_ix_j\)的系数只会增加,因此根据Schwartz-Zippel引理,随机一组\(x_{1\sim26}\)对应a~z即可。然后,对于NTT的过程,有两个卡常的点:一是点积reverse后转卷积的过程是舍......
  • 插头 dp 小记
    这里默认各位都会轮廓线dp。引入Luogu5074EattheTrees题意:给出\(n\timesm\)的方格,有些格子不能铺线,其它格子必须铺,可以形成多个闭合回路。问有多少种铺法?\(2\len,m\le12\)考虑如果每个格子和相邻格子(包括边缘)的衔接都合法,最终一定能形成若干个闭合回路,因此我们只......
  • 斯坦纳树小记
    斯坦纳树问题平面上有一些点,其中一些是关键点。给出一些点之间的线段,选择一些线段连通这些关键点并且线段总长度最小。最小斯坦纳树——在图论中的运用一张无向连通图,选择一个部分结点的生成树,结点包含点集\(S\),求生成树的最小边权和。\(n\le100,\spacem\le1000,\space|S......