RMQ

一个小专题鸭。

线段树求 RMQ 唯一的好处就是带修,静态问题上线段树就是垃圾。

所以我们有 ST 表,\(O(n\log n)\sim O(1)\) 的时间复杂度的确很优秀,但遇到毒瘤题预处理吃满的复杂度和巨大的空间还是可以被枪毙的。

所以下面是正文。

四毛子算法

因为是一个由四位俄罗斯籍的计算机科学家提出来的基于 ST 表的算法,所以叫四毛子算法。

我们将序列分成 \(B\) 块,每一块建一个 ST 表,同时基于大块建立一个 ST 表,然后再维护块内的前缀最值与后缀最值。

当区间跨块时,我们只需要 \(3\) 次查询就能得到 RMQ. 当区间在块内时,我们只需要查询块内 ST 表就行了。

预处理复杂度 \(T(n)=\dfrac{n}{B}\times B\log B+\dfrac{n}{B}\log\dfrac{n}{B}\).

\(B=\log n\) 时,\(T(n)=O(n\log\log n)\),空间复杂度也是这个,但常数较大。

感觉还是比较鸡肋?

对块内处理的进一步优化 & +1-1 RMQ

四毛子算法已经把块外做到 \(O(n)\sim O(1)\) 了,没优化空间。对于 +1-1 RMQ 问题,即保证原数组的差分数组每一项的绝对值 \(= 1\) 的问题,可以预处理出所有本质不同的序列的最大值位置。

具体地,记 \(0\)\(-1\),记 \(1\)\(+1\),则任意一段差分序列都可以压成一个数 \(S\)。通过枚举所有可能的 \(S\),记录下 \(x\) 在实施这个序列 \(S\) 过程中的最值与 \(x\) 的差。显然因为 \(|S|\le \log n\),所以这一步的复杂度为 \(O(n)\)。接下来,记录每个块压完之后的二进制表示,块内 \(O(1)\) 查表就行了。

大概就是这么个东西:

mnp[0][0]=0;
for(int i=1;i<=B;i++){
	for(int j=0;j<(1<<i);j++){//0: -1, 1: +1
//		mnp[i][j] 表示 +1-1 状态为 j 时最大值距区间起点的距离.
//		注意我们使用末 i 位来代表这个长度为 i 的操作序列, 所以应该是依次执行了最高位开始的每一个操作.
		val[i][j]=val[i-1][j>>1]+((j&1)?1:-1);
		if(mnp[i-1][j>>1]==i-1) mnp[i][j]=mnp[i-1][j>>1]+(j&1);
		else {
			mnp[i][j]=mnp[i-1][j>>1];
			if(val[i][j]>val[mnp[i][j]][j>>i-mnp[i][j]]) mnp[i][j]=i;
		}
	}
}

Ri 给了个拓展,见 这篇 文章,但是我暂时没看懂,我太菜了。

结合笛卡尔树

其实我是先学的 Treap 再学的笛卡尔树,所以话说得比较诡异。

笛卡尔树是满足对值构成堆,对下标构成二叉搜索树的树。也就是在不改变原数组(或者说是树链)中序遍历的前提下,把 Ta 捏成一个堆。

于是因为中序遍历没有被改变,所以查询 \([l,r]\) 的 RMQ 就等于查询 \(l\)\(r\) 在笛卡尔树上的 lca 的权值。

这玩意在随机情况下暴力可以做到 \(O(\log n)\),但在极端情况下并没有改变复杂度。不过,众所周之欧拉序可以花费 \(O(n)\) 的代价把 lca 转化为等规模的 RMQ,且欧拉序就是一个 +1-1 的序列。

所以我们通过 建立笛卡尔树 + 构建欧拉序 + 构建 ST 表 + 压位预处理,实现了 RMQ \(\to\) LCA \(\to\) +1-1 RMQ \(\to\) \(O(n)\sim O(1)\) 的任意序列静态 RMQ 的做法,很棒(常数大成啥了)。

插播

鉴于我并没有学过笛卡尔树,所以把线性建树的笔记放这里了。

其实很简单。首先你需要在不改变中序遍历的情况下将原数组捏成一个堆,所以按下标从小往大建树过程中每一次都应该把当前点放在右链的末端。

从而我们维护这一条右链。每一次从下往上遍历这根链,当遇到一个 当当前点的父亲的点时(即作为当前点父亲时满足堆性质),我们就让当前点成为这个点的右儿子,然后把它原来的右儿子变成自己的左儿子保证中序遍历不变。如此右链中已经被遍历过的点全部被清空,当前点加入右链。这事实上就是一个单调栈的过程,即这个“配”当父亲的点就是右链从下至上第一个 \(><\) 当前点的点。

因为每个点进出右链至多一次,总复杂度为 \(O(n)\).

魔法

lxl 出品:P3793 由乃救爷爷.

我们发现上面 分块 + ST 表 + 处理前后缀的方法很棒,沿用过来。

对于块内查询我们直接摆烂跑暴力。

这样摆烂是有道理的,因为我们块长取的 \(\log n\),所以区间两个端点落在同一个块的概率并不大,且就算落在一起复杂度也不会高于 \(O(\log n)\).

精细计算一下你发现单次查询 Ta 的期望复杂度是 \(T(n)=(1-\dfrac{B}{n})\times O(1)+\dfrac{B}{n}\times O(B)\),当 \(B=\log n\) 时算出来还是 \(O(1)\).

于是这玩意在随机数据下很对。然后数据不随机时,我们可以微调块长(加一个很小的随机数)让出题人没法对着块长卡,而且卡了你他还可能让暴力冲过去,所以挺安全的。

嗯就是一种很神奇的期望 \(O(n)\sim O(1)\) 的 RMQ。

就算复杂度稍微下降了一点,这 \(\log\) 倍的空间优化可是实打实的。

posted @ 2025-11-26 19:45  xwxabc  阅读(4)  评论(0)    收藏  举报