Examples

浅谈一类信息的暴力重构手法

loj#6515. 「雅礼集训 2018 Day10」贪玩蓝月

背包支持类似栈的加入与撤销(由于是最优化,不太能直接删除),而题目要求维护双端队列式的操作,这是一个经典问题——双栈模拟双端队列(也叫 baka trick)。

直接给出方法:维护两个栈,两栈的拼接即为我们维护的队列,照常进行大部分操作,若某一栈在空的情况下进行 pop,我们将另一栈沿中点劈成两半,暴力重构两栈信息。

分析复杂度:令势能为两栈大小差的绝对值,修改至多影响 \(1\) 的势能,而一次重构会消耗 \(L\) 的势能,产生 \(O(L)\) 次更新,因此总更新次数是 \(O(n)\) 的。

类似题目:JAG2018 Summer Day2 D Knapsack And Queries

一个结构类似的问题:牛客练习赛 102F 黑鸟,由于与本文主题无关,不在此赘述。

CF710F String Set Queries

由于答案有可减性,删除只需维护两个结构,分别表示加/删的字符串,答案即两个结构答案之差。

转化后的问题是经典的二进制分组,其方法为:假设字符串集合大小为 \(S\),我们将序列拆成 \(\operatorname{popcount}(S)\) 段,且长度分别为 \(S\) 从高到低为 \(1\) 的二进制位。此时插入操作相当于 push 一个大小为 \(1\) 的段,每次 push 结束后我们检查最靠后的两段段长是否相同,若相同则合并两段,暴力重构信息。

分析复杂度:一个数所在段段长不减,因此其只会贡献 \(\log n\) 次操作次数,总操作次数 \(O(n\log n)\)

uoj#46. 【清华集训2014】玄学

由于有区间询问,我们考虑将上面二进制分组的结构搬到线段树上去,每个结点只有 \(l\) 个修改,于是值域被划分段数不超过 \(2l+1\),每一段维护一个最终线性变换。

沿用上面二进制分组的方法,合并两段即线段树上的 pushup,可以归并合并,查询用线段树结构分成 \(O(\log)\) 段区间,每段二分一下即可。

复杂度 \(O(n\log n+q\log^2n)\)

bonus:P8525 [Ynoi2078] 《A Path Towards Autonomous Machine Intelligence》阅读报告(更新中...,将查询加速到 \(O(q\log n)\)

类似分散层叠的过程,已经确定下来的结点的序列保存每个位置在左右儿子的来源,未确定的结点是一段右链,而修改均为后缀修改,我们维护一个从下到上的分散层叠,每个链上结点为其父亲的 \(\frac 14\) 与其左儿子的 \(\frac 12\) 的归并,于是只需一次二分以及 \(O(\log)\)\(O(1)\) 的定位。

uoj#191. 【集训队互测2016】Unknown

与上一题很类似,但由于有 popback 不能直接用线段树维护。

考虑每个结点在其同层后继能 pushup 时再进行 pushup,每层都有恰好一个结点查询时要拆分成左右儿子的查询,因此询问复杂度仍为 \(O(q\log^2n)\)(多一个 \(\log\) 是因为本题还要在结点凸包上二分)。

分析复杂度:每层相邻两次重构之间均需要 \(O(len)\) 次修改,重构一次是 \(O(len)\) 的。

uoj#693. 【UR #23】地铁规划

单栈模拟队列。

我们想沿用双栈模拟队列的方法,但此时我们无法分开两个栈,不妨直接将左右两栈按某种顺序归并在一个栈中,只记录每条边属于左栈还是右栈(记作 \(0/1\))。

左端点左移时我们需保证栈顶是 \(0\),右端点右移则无所谓。每次左端点左移时直接 pop,据说这样是 \(O(m\sqrt m)\) 的,不过还是无法通过。

结合二进制分组的思想,我们可以每次 pop 时多 pop 一些数出来把 \(0\) 放上去。具体地,若此时有 \(c\)\(0\),我们 pop \(\operatorname{lowbit}(c)\)\(0\) 出来,然后将中途弹出的 \(1\) 放下面 \(0\) 放上面。

分析复杂度:将 \(0\) 元素二进制分组,那么 \(1\) 被 pop 次数是其前面的组数,而 \(0\) 元素可以定义势能为所有出现过的组的长度和,一次 pop 会将一个势能为 \(2^k\) 的组拆成 \(2^{k-1}+2^{k-2}+\cdots+1\),势能恰好减一。

于是复杂度 \(O(m\log m)\)

posted @ 2023-05-18 21:55  xiaoziyao  阅读(300)  评论(0编辑  收藏  举报