杂项随记
挺多不知道写在哪但是可能会有点用的 trick。
1. 特殊式子考虑组合意义
P1758 [NOI2009] 管道取珠
注意到答案形式是 \(\sum a_i^2\),可以认为是两个人取出相同序列的方案,暴力 dp 是 \(f_{i,j,k,p}\) 表示两个人分别取到的位置,发现有 \(i+j=k+p\),所以省掉一维,复杂度 \(O(n^3)\)。
2. 折半警报器/二进制警报器
折半警报器
考虑以下问题:
给定一个长 \(n\) 的序列,每个位置初始为 \(0\),支持以下操作:
给某个位置 \(+k\)。
给某 \(k\) 个位置加装警报器,当这些位置累计被加超过 \(V\) 时报警。
对于每个 \(1\) 操作找出哪些报警器报警了,\(k\) 很小。
允许离线的话显然可以整体二分做到时间复杂度 \(O(n\log n)\),空间线性。考虑强制在线的问题。
为什么不能每次暴力 check 重构呢,显然每次 \(+1\) 的话就被卡到 \(O(n^2)\) 了,考虑把 \(2\) 操作的警报器拆成 \(k\) 个报警值为 \(\lfloor \frac{V}{k} \rfloor\) 的小警报器,显然在其中任意一个警报器都没报警时,大警报器也不会报警,每次有一个小警报器报警时 \(O(k)\) 暴力重构所有警报器,整个过程需要一个堆维护,显然每次重构后至少有 \(V \to \frac{k-1}{k} V\),所以重构次数不超过 \(O(\log_{\frac{k-1}{k}}V) = O(\frac{\ln k}{\ln k - \ln k-1}) = O(k\log V)\),每次报警需要 \(O(k\log q)\) 的重构,复杂度 \(O(k^2\log V\log q)\)。
P7603 可以直接套用这个做法,复杂度 \(O(n\omega^2 (n)\log V\log m)\),可以在 10s 内通过。
二进制警报器
由 zak 发明,blog。
发现折半警报器比较劣的点在于我们用堆维护了所有警报器,并且每次需要 \(O(k)\) 的重构,而二进制警报器很好的处理了这两个问题,做到了均摊 \(O(k\log V)\)。
记警报器记录的数为 \(a_{pos_1},a_{pos_2},\dots,a_{pos_k}\),设定一个阈值 \(h\),当某个 \(a_{pos_i} \gets a_{pos_i} + w\) 时,如果其经过了 \(2^h\) 的倍数,那么我们就报警经过 \(2^h\) 的倍数个数次,如果当前阈值 \(h\) 可以让所有数在不报警的情况下达到 \(V\),就令 \(h \gets h-1\)。
对于每个 \(h=h_0\),这样的报警次数是不超过 \(O(k)\),首先显然 \(V - \sum a_{pos_i}\) 为 \(O(k2^{h_0})\),而每个小警报器经过两次报警一定至少增加了 \(2^{h_0}\),所以报警次数不超过 \(O(k)\),因此总报警次数不超过 \(O(k\log V)\),和折半警报器相当,但是我们将值域限定到了 \(2\) 的 \(k\) 次幂上。
那么我们就不需要用堆来维护了,开 \(O(n\log V)\) 个 vector 即可,这样我们就优化掉了 \(\log q\)。
如何优化重构复杂度呢,考虑瓶颈在于我们不好判断什么时候不重构不行了,即不重构的话最大和就可能到达 \(V\) 了,此时我们要令 \(h \gets h-1\),因此我们维护一个当前在不报警情况下的最大可能和 \(lim\),每次判断 \(\lim\) 是否合法即可,不合法再重构,就均摊到了值域上,省掉了一个 \(k\),至此,复杂度降为 \(O(k\log V)\)。
不咕了,写完了,nxt(x,y) 为 \(>x\) 的下一个 \(2^y\) 的倍数,好像还比折半警报器短一点。
QOJ7415. Fast Spanning Tree
随便做吧,把 \(n\) 个点视作 \(n\) 个警报器,每次做一下警报器的合并即可,可以链表。
咕咕咕,懒得写了。
QOJ8035. Call Me Call Me
集合大小很大但是是区间,考虑拆到线段树上,然后就做完了,可以多叉树或者根号分治。
咕咕咕,懒得写了。
3.摩尔投票
还没看到什么更典型的题。
CF643G Choosing Ads
考虑每次只需要选出不超过 \(k\) 个颜色,此处 \(k \le 5\)。
已知摩尔投票法选出区间众数是每次选择两个不同数删掉,这里简单扩展一下,每次删 \(k\) 个不同数自然就得到了前 \(k-1\) 众数。
所以对于两个区间可以做到 \(O(k^2)\) 的合并,然后就做完了,上线段树,复杂度 \(O(nk^2\log n)\)。
4.\(\operatorname{mex}\) 相关
随机写一点。
排列中 \(\operatorname{mex}(l,r) = \min([1,l-1],[r+1,n])\)
联合省选 26D2T1
极小 \(\operatorname{mex}\) 区间只有 \(O(n)\) 个
在线区间 \(\operatorname{mex}\) 可以主席树二分 \(O(\log n)\),可以 \(O(n\log n)\) 求出所有极小 \(\operatorname{mex}\) 区间。
5. 平衡树有交合并
无值域不交要求的可并FHQ ——murder_drones cf 上的证明
其实就是暴力,将两个合并的树拆成若干不交的段,再暴力 merge 起来,可以通过势能分析说明复杂度是 \(O(n\log^2 n)\) 的。
需要处理值相同的节点,不同的题处理办法可能不一样,放一段示例代码:
inline ll Merge(ll x,ll y)
{
if(!x||!y) return x|y;if(t[x].val>t[y].val) swap(x,y);pd(x),pd(y);
ll L1=t[x].l,R1=t[x].r,L2=0,R2=0,equ=0;sp(y,t[x].sum-1,L2,R2),sp(R2,t[x].sum,equ,R2);
if(equ) /*do something*/;t[x].l=Merge(L1,L2),t[x].r=Merge(R1,R2);return ps(x),x;
}
P11622 [Ynoi Easy Round 2025] TEST_176
给点长 \(n\) 的序列 \(\{a\}\),\(q\) 次询问,要求 \(x\) 从 \(l\) 开始走到 \(r\),每次令 \(x \gets \max(x,a_i-x)\),最终 \(x\) 的值。
\(1 \le n \le 2\times 10^5,0 \le |x|,|a_i| \le 10^{13}\)。
考虑离线,一个 trick 是对于这种问题我们可以维护一个数据结构扫描线时动态维护每个点所有询问的值。
具体地,分类讨论一下,对于 \(x \in (- \infty,\lfloor \frac{a_i}{2}\rfloor]\),需要令其 \(x \gets a_i - x\),其余部分不变。
可以使用平衡树维护分裂后修改的操作,但是两部分操作后值域可能有交,我们需要带交合并。
考虑暴力维护合并,我们将两部分拆成若干无交的连续段,再暴力 merge 起来,这样可以通过势能分析证明复杂度是均摊 \(O(n\log^2 n)\) 的。
为了保证复杂度,需要对平衡树内的数去重,并查集即可,每次有相同数删掉一个并连边。
CF702F T-Shirts
\(n\) 个物品,有价值和质量,\(k\) 个人分别有 \(b_i\) 元钱,会按照质量从大到小,价值从小到大排序后顺序选取可选取的,求最后每个人买的物品数。
\(1 \le n \le 2\times 10^5\)。
和上题基本一样吧,拆一下得到需要分成三个部分讨论,\([0,c),[c,2c),[2c,+\infty)\)。
其实这题不用带交合并的,因为只有中间部分合并后可能会有交,而且每个数不会修改超过 \(\log V\) 次,但是反正写都写了,扔个带交合并上来。
对于相同数,我这里直接建了个图最后跑一下拓扑求答案即可,注意要下放全树标记,复杂度 \(O(n\log n\log V)\)。

浙公网安备 33010602011771号