2024.9.[23, 24]训练记录
23 上午
whk。
辅助角公式。
诱导公式。
23 下午
莫队:原序列分块。
询问排序:第一关键字为左端点所在块的编号,第二关键字为右端点编号。
回滚莫队:适用于增加或删除操作其中一个复杂度较大,但另一个较小的情况。可以做到只使用一种操作。
排序后按照左端点的块编号一块一块做。
排完序后,同一块内的询问的右端点就是有单调性的。
如果左右端点都在这一块内的话,长度不超过 \(O(\sqrt {n})\),可以暴力做。(这里要分析一下复杂度)
如果是单调删,应该也可以不暴力做,归到一块。
在每次移完这一个询问的指针后,下一次询问,右端点可以在此基础上继续移。
此时。将左指针撤销到这一块的边上。
具体哪一边看是单调删还是单调插。相当于左指针每次从块的边上 删/插 到询问的左端点。
撤销操作的复杂度也需要单独分析。
省流:块内询问,右端点单调移,左端点每次撤销到边上再移。
其实就是把 插入/删除 操作变成了 插入/撤销 和 删除/撤销。只要 撤销 的复杂度和 插入/删除 其中一个复杂度都可接受。就可以回滚。
23 晚上
莫队二次离线:
纯脑瘫算法。好难好难好难。
对于 插入/删除 复杂度较大的情况,可以把每次 插入/删除 再次离线下来。
设 \(f(x, [l, r])\) 为当前点 \(x\) 的 插入/删除 对区间 \([l, r]\) 的贡献。
则有 \(f(x, [l, r]) = f(x, [1, r]) - f(x, [1, l])\)。
指针左右移的贡献是 \(f(x, [l, x - 1]),f(x, [x + 1, r])\)。
右移:\(f(x, [1, x - 1]) - f(x, [1, l - 1])\)。
左移:\(f(x, [1, r]) - f(x, [1, x])\);
将所有的操作都转化成前缀,预处理贡献。
\(f(x, [1, x - 1])和f(x, [1, x])\) 可以直接预处理之后存。
但是存每个 \(f(x, [1, l - 1])\) 和 \(f(x, [1, r])\) 的空间复杂度是 \(O(n \sqrt{n})\)。
这里因为莫队每次指针都会动一个连续区间,可以存每一个端点 \(l-1\) 和 \(r\) 的移动区间,而不是记区间内的每个点。
具体存法是对于每个 \(l-1\) 和 \(r\) 开 vector
,存入移动区间。
大概过程:
1、分块。
2、预处理 \(f(x, [1, x])\),\(f(x, [1, x - 1])\)。
3、莫队一遍,统计预处理完的答案,同时记录指针移动的区间,存下来。
4、处理刚刚存的移动区间。
5、将这些区间计入答案。
6、前缀和,输出。
因为预处理的是每次的贡献,也就是答案的变化量,所以最终答案是前缀和。
写起来要注意的细节:
记录区间那个地方,最终的贡献是正的还是负的由前缀和式子里的正负号和指针移动是插入还是删除共同决定,特别细节,要好好想。
前缀和是按照询问排序后的顺序先做完,再放到输入顺序的答案数组里去。
为什么要二次离线:
设原来 插入/删除 的复杂度是 \(O(k)\)。
预处理 \(f(x, [1, x])\),\(f(x, [1, x - 1])\) 是 \(O(nk)\) 的。
同时,预处理 \(f(x, [1, l - 1])\) 和 \(f(x, [1, r])\) 时,只有在遍历到 \(l-1\) 和 \(r\) 时,做一次 \(O(k)\) 的操作。也是 \(O(nk)\) 的。
这样把原来莫队 \(O(n \sqrt{n} k)\) 的复杂度变成了 \(O(n \sqrt {n} + nk)\)。
感觉真正自己做的时候,面对不同题目,能不能只做 \(O(nk)\) 次操作是看问题类型的,并没有那么泛用。
而且很难调。输出中间结果看不出来什么东西。
实用性不如回滚(?)。
总结:我太菜。