Loading

数据结构 杂题 7.14

前言

策略就是放弃模拟赛, 搞完这两天的授课内容
这样后面勉强还能跟上

\(\text{号家军 OJ S0039 堆}\)

策略
停滞
心态
题解仅供参考

思路

按题意直接做就可以达到 \(\mathcal{O} (qn \log n)\)

首先简化问题
\(q\) 个询问, 每个询问 \(n\) 次操作, 第 \(i\) 次操作找到 \(a [1 : i + p - 1]\) 中的最大值 \(v_{\max}\), 统计 \(ans \gets (-1)^{i - 1} \times v_{\max}\), 然后在原序列中将 \(v_{\max}\) 赋值为 \(-\infty\), 询问这个 \(ans\)

怎么处理?
观察到这类似于第 \(k\) 大问题, 不过每次操作完要加上一个边界的拓张
那么我们首先考虑能不能直接用第 \(k\) 大的方法处理? 也就是每次取出最大值, 加入之后的次大值
发现是不行的, 因为这样做无论怎样都要维护一个堆, 带 \(\log\)

我草, 然后就看到你妈一个超级牛逼的东西, \(a_i \leq n\)
但是这个东西好像并不好用啊
发现单个询问里, 一次操作竟然要做到 \(\mathcal{O} (1)\), 这是啥啊?

好吧看了题解感觉还是超模
不难发现每次拓张时, 如果拓张出来的那个数大于现在堆里的所有数, 那么本轮肯定就选它, 接着轮下去, 直接视作它不存在
否则我们把它拼到某一个区间上之后, 选择堆中最大的数一定要比之前堆中最大值要小
既然最大值单调不增, 可以用指针 \(p\)\(n\) 开始向下扫描
用计数数组 \(cnt\) 维护集合中每个值的出现次数
每次加入新元素时, 如果成为了最大值直接删除
否则通过 while (!cnt[p]) p--; 找到当前最大值


让我们来理一下

我们还是采用第 \(k\) 大的手段
\(i\) 此操作, 我们先找到堆中最大值 \(\{[l, r], a_m\}\)
然后拆分成 \(\{[l, m), a_x\}, \{(m, r], a_y\}\) 加入堆中, 找到堆中 \(r = i + p - 2\) 的, 把它修改成 \(r = i + p - 1\)

考虑优化, 显然要利用 \(a_i \leq n\)
如果没有拓张操作我们应该怎么做? 可以理解成 \(p = n\)
不难发现因为第 \(k\) 大值显然不大于第 \(k - 1\) 大值, 我们只需要维护一个值域指针就能扫出所有可能的第 \(k\) 大值
本质上是在用值域上的扫描处理这个堆

加上拓张操作, 我们惊喜的发现, 如果加上之后最大值上升, 那么这个时候我们可以直接统计拓张出来的那一个然后把他删除
其他情况下最大值仍然不增, 可以扫描值域处理
你可以发现, 这种情况下操作完之后堆的形态没有发生任何变化

总结

本质上是基于「堆顶不增」的原理, 如果 \(\rm{push}\) 进去了一个较大的数就直接弹出
删除一个元素, 除了把一个区间拆分成两个区间之外, 也可以规定之后求得的最大值不得大于当前值

看到 \(a_i \leq n\) 这样的性质一定要思考扫值域相关

\(\text{「NOIP2017」列队}\)

思路

每次对 \((x, y)\) 的操作, 显然可以看成把第 \(m\) 列的第 \(x\) 个数添加进第 \(x\) 行的备选队列, 把 \(A_{x, y}\) 添加进第 \(m\) 列的备选队列, 然后对第 \(m\) 列和第 \(x\) 行的一部分打上循环移动的标记

首先我们处理循环移动的标记问题
0 1 2 3 4 5 6 7 8 9 | 10 11 12 13
0 1 2 3 4 6 7 8 9 10 | 11 12 13
0 0 0 0 0 1 1 1 1 1
0 1 2 4 6 7 8 9 10 11 | 12 13
0 0 0 1 2 2 2 2 2 2
1 2 4 6 7 8 9 10 11 12 | 13
1 1 2 3 3 3 3 3 3 3
1 2 4 6 7 8 10 11 12 13
1 1 2 3 3 3 4 4 4 4

1 1 1 2 2 3 3 3 3 3

以上, 不难发现维护偏移量是很困难的, 中间有大量的特殊点
如果直接维护一个点是否存在, 可以更方便的维护出一个序列的第 \(i\) 个点\((\)线段树二分\()\), 然后做删除查询操作即可
现在问题是如果一起维护, 要处理 \(n + 1\) 个序列的存在性, 不太可能实现
但是发现我们先模拟一遍, 把问题直接转化成询问, 删除, 追加操作, 然后离线下来一个一个序列处理即可


总结一下
首先, 我们简化问题
发现可以拆分成 \(n + 1\) 个序列\((\)每行的前 \(m - 1\) 个数和最后一列\()\)删除第 \(i\) 个数字, 末尾加入数字, 查询第 \(i\) 个数字的问题
然后发现这个可以用假删除的思想直接处理, 为了空间的复杂度将操作离线下来, 每次只处理一个序列的结果, 最终复杂度 \(\mathcal{O} (q \log n)\)

总结

假删除的操作, 和值域思想也很有关系

复杂的问题往往拆成一些小问题处理
这题很考验你简化问题的能力, 一定要把问题拆分到序列上并且刻画操作才能解决问题
往往要思考, 你现在在思考的问题到底是什么?

\(\text{号家军「OJ S0016」法术}\)

思路

显然 \(h \leq n\) 是一个关键性质

如果问题不随机, 也就是 \(L = R\) 时应该怎么做?
不难发现相当于在集合中找 \([1, d], [d + 1, 2d], \cdots\) 这些值域区间中, 第一个不存在数字的是哪一个区间
随着插入, \(ans(d)\) 显然不降, 且 \(ans(d) \leq \lceil n / d \rceil\)
因此 \(ans(d)\) 只会变化 \(\lceil n / d \rceil\) 次, 所有 \(d\) **只会变化 \(\mathcal{O} (n \log n)\)

如果用树状数组维护 \(ans(d)\) 数组
对于原问题的区间查询 \([L,R]\), 答案为 \(\displaystyle\dfrac{1}{R - L + 1} \sum_{i = L}^{R} ans(d)\)
关键: 如何高效计算 \(ans(d)\) 的变化时间点

定义 \(value(k,d) = \max\Big\{\min\{time[1 : d]\},min\{time[d+1 : 2d]\},...,min\{time[kd−d+1 : kd]\}\Big\}\)
其中 \(time\) 数组记录当前点第一次被加入加入时间
因此:
\([ans(t,d) > k] = [t \geq value(k,d)]\)
也就是当前这些区间中都已经有值了

从而: \(ans(t,d) = 1 + \sum\limits_{k = 1}^{\lceil n/d \rceil} [t ≥value(k,d)]\)

我们只要计算出所有 \(value(k, d)\), 就能知道 \(ans\) 的变化时间
计算所有 \(value(k,d)\): 需要进行 \(\mathcal{O}(n log n)\) 次区间 \(\min\) 查询, 使用 \(\rm{ST}\) 表解决
然后我们扫描操作, 不断更新 \(ans(d)\), 每次统计即可


注意到 \(h \leq n\) 作为关键性质

首先处理确定 \(d\) 的查询操作, 期望可以视作求每种 \(d\) 的答案之和
多半要放到值域区间上去考虑, 不难发现最终等价于 \([1, d], [d + 1, 2d], \cdots\) 这些值域区间中, 第一个不存在数字的是第几个区间

比较牛的一点是对于所有 \(d\), 区间个数之和是 \(\mathcal{O} (n \log n)\)
如果直接处理, 每次操作暴力查询是 \(\mathcal{O} (mn \log n)\) 的, 也就是对每个区间标记是否存在数字
我草这不是糖龙做法

为了接下来更好表示, 我们记 \(t_i\) 表示数字 \(i\) 最早被加入的时间, \(ans(d)_t\) 表示 \(t\) 时刻 \(d\) 对应的答案

「第一个满足条件」这种表述, 我们一般用 \(ans(d)_t > x\) 来刻画
也就是

\[ \begin{align} [ans(d)_t > x] = \Big\{ans(d)_{t} > x - 1\Big\} \And \bigg\{\min t\Big((x - 1)d: xd\Big] \leq t\bigg\} \end{align} \]

考察 \(ans(d)_t \geq ans(d)_{t - 1}\) 的性质
结合非常关键的一个性质 \(ans(d)_t \leq \lceil n / d \rceil\), 不难发现 \(ans(d)\) 只变化 \(\lceil n / d \rceil\)
因此整个 \(ans\) 数组只变化 \(\mathcal{O} (n \log n)\)\(\hspace{11cm} (2)\)

因此我们只要能维护 \(ans(d)_t\) 中有哪些发生变化, 变化了多少, 就能实时计算出 \(ans\) 而不超时

现在利用第一个性质
\(apr(k, d) = \max \bigg\{ \min\limits_{x = 0}^{k} t\Big((x - 1)d: xd\Big] \bigg\}\)
现在 \([ans(d)_t > x] = [apr(x, d) \leq t]\)
于是 \(ans(d)_t = \sum\limits_{x = 1}^{\lceil n / d \rceil} [apr(x, d) \leq t]\)
维护指针 \(p_d\) 表示现在 \([apr(x \leq p_d, d) \leq t]\), 每次动态维护 \(ans(d)\) 可以视作维护指针, 但是事实上我们只用把所有 \(apr(x, d)\)排序之后维护即可
根据之前的证明, 总的变化次数是 \(\mathcal{O} (n \log n)\) 的, 加上排序, 复杂度 \(\mathcal{O} (n \log^2 n)\)

因为我们要做区间求和, 所以把它放到树状数组上处理, 总复杂度 \(\mathcal{O} (n \log^2 n)\)

总结

值域范围较小的时候, 值域作为下标往往是一个重要的切入点
第一个什么什么一般用 \(ans(d)_t > x\) 来刻画

经典的调和级数复杂度, 长度为 \(1, 2, 3, \cdots n\)

有单调性的数组, 其上界等于变化次数上界
本题关键应该是发现变化次数是 \(\mathcal{O} (n \log n)\) 的, 于是想到直接维护变化点
注意我们只能维护变化点的具体位置而不能再用类似指针的方法, 事实上类似本题中的变化点是非常好维护的

\(\text{「雅礼集训 2017 Day1」市场}\)

思路

发现区间除法没有结合律无法维护
但是一直进行全局区间加法和除法, 序列极差会单调减小, \(\mathcal{O} (\log V)\) 次操作之后极差 \(\leq 1\)
例子:
\([1,10] \stackrel{\div 2}{\longrightarrow} [0,5] \stackrel{+ 5}{\longrightarrow} [5,10] \stackrel{\div 4}{\longrightarrow} [1,2] \stackrel{+ 2}{\longrightarrow} [3,4] \stackrel{\div 2}{\longrightarrow} [1,2]\) 然后循环

考虑这个性质的应用
先正常使用线段树, 除了除法之外的操作都能正常处理

  • 对于除法: 先把操作下放到 \(\mathcal{O} (\log n)\) 个节点区间上
  • 如果节点区间的极差 \(>1\), 直接两边递归, 这会让区间极差减半
  • 如果极差\(\;= 0\), 显然不再需要递归
  • 如果极差\(\;= 1\): 操作后极差\(\;= 0\) 显然可以停止递归;操作后极差\(\;= 1\) 其实是区间减去同一个值, 可以打上区间加标记后停止递归

复杂度分析:
唯一复杂度不是 \(\mathcal{O} (\log n)\) 的部分是「极差 \(>1\) 时的递归」
复杂度分析的核心是势能函数: \(\sum_{\text{节点}} \log (\text{节点区间极差})\)
每次「极差 \(>1\) 时的递归」会让势能减小 \(1\)
加法和除法操作会增加势能: 它会把 \(\mathcal{O} (\log n)\) 个线段树节点\((\)包含某个节点\()\)的极差重置为 \(\mathcal{O} (V)\), 所以势能增加 \(\mathcal{O} (\log n \log V)\)
因为势能的总体增量是 \(\mathcal{O} (q\log n \log V)\), 所以「极差 \(>1\) 时的递归」的次数也是 \(\mathcal{O} (\log n \log V)\)
总复杂度: \(\mathcal{O} (\log n \log V)\)

总结

除法的性质, 每次把值域问题至少缩小一半
线段树上每个节点只被 \(\log n\) 个节点包含, 一次修改操作往往只会影响 \(\log n\) 个节点

\(\text{号家军 OJ S0034 老题重做}\)

思路

点对问题, 考虑每个点对点对的贡献
在右上区域中, 按 \(y\) 坐标从小到大扫描, 只有当 \(x\) 坐标是前缀最小值时, 该点才能与 \((x_i,y_i)\) 产生贡献
显然可以扫描线 + 一个可以维护区间前缀最小值数量的数据结构

考虑一个可以维护区间前缀最小值数量的数据结构

总结

点对问题, 考虑每个点对点对的贡献

维护前缀最小值的神奇解法, 本质上是引入一个前缀最小值只会对 \(\mathcal{O} (\log n)\) 个节点产生影响, 而单点修改只会对 \(\mathcal{O} (\log n)\) 个节点引入前缀最小值

\(\text{「北大集训 2021」小明的树}\)

思路

首先考虑判断一个时刻是否美丽, 我们记 \(t_u\) 表示 \(u\) 被点亮的时间
一个时刻 \(t\) 美丽, 仅当 \(\forall t_u \leq t\) 满足 \(\max\limits_{v \in tree(u)} t_v \leq t\)

哦, 这个东西完全不会维护
不妨把时间打到点上, 然后再看, 总比真的顺着时间处理要好得多
这个时候我们就发现, 判断任意一个子树 \(tree(u)\) 在哪段时间是合法的是简单的, 我们只需要求出子树中的最大值, 那么这个子树在 \(t_u \sim t_{\max} - 1\) 这段时间内都是非法的, 不存在非法子树的时间是合法的

进一步简化一下表述, 非法时间段为 \(\bigcup\limits_{u \in tree} [t_u \sim t_{\max})\), 则合法时间为其补集 \(\overline{\bigcup\limits_{u \in tree} [t_u \sim t_{\max})}\)
我们先考虑这个基础上能不能计算美丽时刻的连通块数之和
不难发现连通块的计数方式是

\[C = \left(\sum_{t_u \leq t} [t_u < t_{fa_u}] \right) + 1 \]

因此我们可以直接 \(\mathcal{O} (n)\) 的在不修情况下计算美丽时刻的连通块数之和
但是带修之后计算复杂度限制很大, 不能超过 \(\mathcal{O} (\log n)\) 次, 这种方法基本不能做

考虑修改的影响
修改等价于每次拆出来一个子树然后接到另一个点上

  • 子树内部合法情况不变
  • 子树外部
    • 原本包含子树的子树的合法情况会受到影响
    • 新包含子树的子树的合法情况会受到影响

不难发现关于子树外部的影响, 我们每次都要重新计算 \(t_{\max}\), 几乎是不可能的, 由于树是动态的, 数据结构维护也不太可能
因此这题使用动态树应该挺牛的, 可惜我不会


破防专用分界线


首先考虑离线, 时间作为信息
原题限制转化为

一个子树 \(tree(u)\) 非法的时段为 \([t_u \sim t_{\max})\), 其中 \(t_{\max}\)\(tree(u)\) 中的最大值

直接往下做在上面的尝试中被证明是不行的, 事实上我们做题的过程中应当保证信息性质简单, 要培养对于一个问题需要复杂度的直觉
也就是说, 我们需要对原题限制进行一些改编

不难发现计算连通块个数, 考虑点边转化
有一个结论是, 对于一个森林, 点数减边数等于连通块的个数
因此树是美丽的当且仅当「未被点亮的节点的个数」减去「两端都未被点亮的边的个数」 \(=1\), 我们记为 \(p\)
同理, 被点亮的连通块个数等于「被点亮的节点的个数」减去「两端都被点亮的边的个数」, 我们记为 \(q\)

在添加点的过程中, 我们如果动态维护 \(p_t, q_t\), 那么答案就是 \(\sum\limits_{t = 1}^{n} q_t \times [p_t = 1]\)

接下来考虑修改操作, 先考虑断掉一条边 \((u,v)\) 时对 \(p\)\(q\) 的影响: 由于一个节点是否点亮只决定于 \(i\), 所以边的情况不会影响点的情况记 \(t_i\) 表示点 \(i\) 被点亮的时刻, 则 \((u,v)\) 脱离“两端都未被点亮”状态的时刻就是 \(\min(t_u,t_v)\), 在这之前它都对 \(p\) 有贡献, 因此删去它后会使 \(p\)\([1,\min(t_u,t_v)-1]\) 区间每个数 \(+1\)
它变成“两端都被点亮”的状态的时刻是 \(\max(t_u,t_v)\), 在这之后它都对 \(q\) 有贡献, 因此删去它后会使 \(q\)\([\max(t_u,t_v),n-1]\) 区间每个数 \(+1\)

同理, 如果连上一条边 \((x,y)\), 则它会使 \(p\)\([1,\min(t_x,t_y)-1]\) 区间 \(-1\)\(q\)\([\max(t_x,t_y),n-1]\) 区间 \(-1\)

于是转化成了一个线段树问题
具体地, 对于线段树每个节点 \([l,r]\), 维护 \(mn=\min_{i=l}^{r} p_i\), \(cnt=\sum_{i=l}^{r}[p_i=mn]\)\(sum=\sum_{i=l}^{r} q_i \times [p_i=mn]\) 以及加法标记即可

时间复杂度 \(\mathcal{O}((n+m)\log n)\)

总结

这种随着时间修改的问题, 一般直接把时间打上去考虑
连通块计数考虑点边转化

找性质和转化问题时应当注意信息的维护复杂度

posted @ 2025-07-21 19:30  Yorg  阅读(28)  评论(0)    收藏  举报