单调栈

概述

  • 单调栈,顾名思义,是元素满足单调性的栈。其主要用于预处理各种和偏序有关的信息。

  • 特别地,我们将悬线法视为单调栈的一个变形,也在这里谈及。

  • 所有二维平面问题采用 OI 坐标系。

实现原理

  • 在准备加入元素时,将栈顶元素弹出直到加入后仍然满足单调性。

例题

P5788 【模板】单调栈/P1901 发射站/P2866 [USACO06NOV]Bad Hair Day S

  • 都差不多,合起来谈吧。。

  • 题意:对序列中每个元素求前驱(前面第一个更大的元素的下标)和后继(对称定义)。

  • 正着扫一遍,维护栈中的点单调不增,所有元素在被弹出的时候得到其后继。同理可以求前驱。

  • 另一种实现是倒着扫,暴力向后跳,如果比当前的 \(nxt+1\) 更大,就可以把 \(nxt\) 扔到 \(nxt_{nxt+1}\) 去,容易看出这是一个访问即删除(之后会越过这个点)的结构,显然也是均摊 \(O(1)\)。事实上,这就是悬线法(“极大延伸”)。

  • 有时序列上的笛卡尔树性问题需要我们求支配区间。主要应用就是在这里。

UVA1619 感觉不错 Feel Good/P2659 美丽的序列

  • 题意:求 \(\min_{i=l}^r a_i\times \sum\limits_{i=l}^r a_i\) 最小的一对 \((l,r)\),相同则最小化区间长度,仍然相同则最小化左端点下标。

  • 数据范围:\(n\leqslant 10^5,a\in [0,10^6]\)。多测,任意两测之间空一行,但末尾不能有空行,不能有行末空格,反正就是 UVA 的(炎国粗口)格式。

  • 显然可以悬线法,爱怎么叫都好,通过寻找前驱后继找到极大延伸。然后直接处理?

  • 事实上有坑点:必须钦定 \(0\) 的延伸是它本身那格,否则全 \(0\) 的数据中区间长度/左端点可能是错的。事实上,我们可以使用如下的悬线法代码:

Fort(i,1,n) if(a[i]) for(int j=l[i]-1;j && a[j]>a[i];j=l[j]-1) l[i]=l[j];
foRt(i,n,1) if(a[i]) for(int j=r[i]+1;j<=n && a[j]>=a[i];j=r[j]+1) r[i]=r[j];
  • 这里的悬线法逻辑是,不断试探是否比下一个位置更小(或等于,这里是经典的左边到小于等于,右边到小于),如果可以那么它能延伸的我都能延伸。当然,直接特判也行。复杂度 \(O(n)\)

P4147 玉蟾宫/P1950 长方形/P1578 奶牛浴场

  • 题意:求不包含任何障碍点的最大子矩阵。

  • 一个简单且符合直觉的做法是,对每个点维护极大的向上延伸 \(up\),对 \(up\) 做一遍上一题的悬线法,得到“高度恰为 \(up_{i,j}\),下界为 \(i\) 的矩形的极大左右延伸”,然后计算之。显然复杂度为 \(O(nm)\)

  • 显然这也是支持计数的。但有些毒瘤会觉得这还是太慢了,我们要操作它一下...

  • 首先,上面的做法的正确之处,在于我们枚举了所有有代表性的子矩阵。不妨定义不含障碍点的子矩阵为合法子矩阵,于是有代表性的子矩阵应当是所有不被其他合法子矩阵包含的合法子矩阵,显然它们都是不可扩张的,故不妨称为极大子矩阵。

  • 因为不可扩张,所以其四界一定是被障碍点或矩形边界卡住的。不妨考虑钦定一个点做矩形的左界,此时上下界即为矩形上下界,不断向右推进直到碰到一个障碍点,此时将现有极大子矩阵结算,然后保留能够继续延伸的一半(譬如从 \(x=3\) 出发,碰到一个 \(x=5\) 的点,就只好保留 \(x\in [1,5]\) 这一半,注意起始点对上下界是无限制的)。到达右边界时也结算一次。

  • 显然这个做法的复杂度是 \(O(K^2)\) 的,这里 \(K\) 为障碍点个数。当原有子矩阵无上下界(即顶满)时,如果新的 \(x\) 和起始障碍点的 \(x\) 相同,那么保留上半边和保留下半边的两种情况都要继续(不妨使用 dfs 实现吧),但显然这种分叉只会有一次,故复杂度是正确的。

  • 注意到我们可能算漏了不少子矩形。上面的过程中,所有(障,障)和(障,界)(两维分别为左右界卡住的原因)的极大子矩阵都被考虑了,但显然剩下两种没有被考虑。不妨从右向左再扫一遍处理(界,障),最后剩下的(界,界)可以将所有障碍点按 \(x\) 排序直接相邻高度差取最大值乘矩形宽度 \(m\) 求最值。复杂度不变,且可以看出这仍然支持计数(这本质上是极大子矩阵的性质,而非具体的枚举极大子矩阵方式的性质)。

  • 显然,这两种算法都可以处理子正方形的特殊情况——极大子矩阵包含的极大子正方形的数量和大小都是容易求出的。另外,应当指出的是,这两种算法事实上都没有做到只枚举极大子矩形,它们只是尽量枚举了一个所有极大子矩形的超集罢了——有理由认为求所有极大子矩形的做法是难以低于 \(O(n^3)\) 的(认为 \(n,m\) 同阶)。

P1169 [ZJOI2007] 棋盘制作

  • 题意:求极大黑白相间子矩形。

  • 显然障碍点法在这里没机会,但将反演法(我认为是关于 \(up\) 的最小值反演)微调仍然能够处理这一问题,只要在求左右延伸的时候保证黑白相间即可,而事实上我们只需要关心它和与它直接相邻的左右格子是否黑白相间即可,其他的都可以交给递推性本身解决。

  • 复杂度 \(O(nm)\)

[AGC005B] Minimum Sum

  • 题意略,就是那个反演。

  • 复杂度 \(O(n)\)

CF1730E Maximums and Minimums

  • 题意:求有多少区间的最大值是最小值的倍数。

  • 数据范围:\(T\leqslant 10,n\leqslant 5\times 10^5,a\leqslant 10^6\)

  • 稍微技巧性的一道题...

  • 首先我们容易把最小和最大的前驱后继都搞出来,但显然通过扫描线求交是一个比较离谱的事情,毕竟区间查 \(\mid x\) 的数的个数似乎本身就很不可做,更不要说推广到历史版本和了(肯定是修改一个上去,把另一个作为查询)。

  • 换一种思路,考虑枚举一个最值,检查另一种最值的控制区间和它的交。注意到 \(a\) 不保证是排列,那么基于均摊的调和级数显然寄了,只能枚举最大值利用 \(\sum\limits d\) 来想办法。

  • 枚举最大值位置,搜出来其所有约数,在 vector 上双指针找到前驱 \(d\) 出现和后继 \(d\) 出现,不妨老规矩钦定向左等于延伸,向右等于不延伸,于是可以简单求交解决问题。\(O(Tnd)\),这里 \(d\) 也就 \(240\) 等价 \(\log^2\) 不到,能过。

  • 谈一下本题的细节吧。规定向左等于延伸,向右等于不延伸,于是所有 \(mn\) 区间对(即 \(([Ll,Rl],[Lr,Rr])\))都是不交的,记当前考虑的最大值位置为 \(pos\)

    • 先考虑和左边的合适 \(d\) 的情况:把 \(pre\) 维护出来,当 \(pre\) 存在时,这个 \(pre\) 本身的区间形如 \(([lmn_{oc_{v,pre_v}},oc_{v,pre_v}],[oc_{v,pre_v},rmn_{oc_{v,pre_v}}])\),然后将该区间与 \(pos\) 的最大值区间对,即 \(([lmx_{pos},pos],[pos,rmx_{pos}])\) 求交,若交得的区间对仍然存在则计数。

    • 然后是和右边的合适 \(d\):发现和右边的每个 \(lmn\leqslant pos\) 的可能都有交,显然这很烦我们希望能只考虑一个点,故全部把贡献扔到最近点上,这代表着对应的区间对中的 \(l\) 区间应当为 \([lmn_{oc_{v,yuan_v}},oc_{v,nxt_v}]\),但 \(r\) 区间应当是事实上所有符合条件的 \(d\) 位置的 \(r\) 区间的并,事实上是...不妨记最远的(虽然最远,但仍然可能在 \(pos\) 左边,如果这样说明右边没有合适 \(d\)\(lmn_{oc_{v,j}}\leqslant pos\)\(j\)\(yuan_v\),于是 \(r\) 区间为 \([oc_{v,nxt_v},rmn_{oc_{v,yuan_v}}]\)。照旧取交。

  • 有点九转分讨的味道,但我觉得还行。

22.9.21 T3 Easy LCA

  • 题意:给出一棵 \(n\) 个点的树和一个排列 \(p_{1\sim n}\),求 \(\sum\limits_{L=1}^n \sum\limits_{R=L}^n dep_{lca(p_L,p_{L+1},\dots,p_R)}\)

  • 数据范围:\(n\leqslant 6\times 10^5\)

  • 先谈谈赛时解法:考虑关于 \(lca\) 反演,把 \(p\) 反过来变成 \(pos_i\) 表示 \(i\)\(p\) 中何处,然后跑 dsu。具体地,在每个点开一个 set,元素为区间,\(set_i\) 中的每个区间的所有子区间的 \(lca\) 都在 \(i\) 的子树内。然后跑启发式合并,如果儿子的某个子区间插入过来后和一个已有的区间相连了,则两端点分别在这两个区间内的所有子区间的 \(lca\) 为这里,计数并合并区间。复杂度 \(O(n\log^2)\),但意外地跑得不慢...

  • 我们知道,关于 \(lca\) 有一个经典结论:若干个点的 \(lca\) 相当于其中 \(dfn\) 最小和最大的两个点的 \(lca\)

  • 于是将 \(p\) 转化为其 \(dfn\),感觉似乎和上一道题差不多...求一下前驱后继,然后...

  • 显然这没法做吧...在上一道题里,枚举最大值和约数后,有意义的最小值区间可以化成两个,容易计数;但这里...所有最小值区间都有意义,问题仅在于是否相交,但我怎么可能提前知道?

  • 考虑树退化成一条 \(1\sim n\) 的链的情况。从左向右扫 \(p\),维护一个单调递增(即深度单调递增)的栈,当准备加入一个新点的时候,将过大的点弹出,可以看出被弹出的所有点作为左端点,加入的新点作为右端点的区间的 \(lca\) 都是新点本身,在弹出过程中我们不断将被弹出点压成新点(点是一个区间 \(l,r\),或者可以认为是一个最小值加一个区间长度即权),最后将新点压回去,并计算新贡献。

  • 举个例子吧。栈里是 \((1,1),(3,1),(4,1),(5,1)\)(都是(最小值,区间长度)),现准备加入一个 \(2\),则依次弹出 \((5,1),(4,1),(3,1)\),各为答案贡献 \(2\),接着将它们压成一个 \((2,4)\),然后计算其和栈内原有点构成的区间的贡献,即以 \(2\) 为右端点,以栈内每个区间中的每个点为左端点的区间的贡献,显然这一值为 \(\sum\limits mn\times w\),这是容易维护的。可以看出,每加入一个点,就统计了以它为右端点的所有区间的贡献,故贡献不重不漏。

  • 容易看出这是可以推广到树上的,将新点和栈顶点 \(lca\) 以下的点弹出,然后把它加进去,其实和虚树的构建方式同构。使用倍增求 \(lca\) 可以得到 \(O(n\log)\) 的优秀复杂度。妙啊!

posted @ 2023-03-07 17:56  未欣  阅读(8)  评论(0编辑  收藏  举报