CDQ分治
一种空间复杂度(和时间常数?)优秀的离线算法。
简介:对于偏序问题,将其中一维(记为 x x x)排序,考虑将当前区间 [ 1 , n ] [1, n] [1,n] 以中点 m i d mid mid 分开,形成子区间 [ 1 , m i d ] [1, mid] [1,mid] 和 [ m i d + 1 , r ] [mid+1, r] [mid+1,r]。处理 [ 1 , n ] [1, n] [1,n] 时只考虑从两子区间出发, x x x 跨越 m i d mid mid 的贡献;然后递归到子区间 [ 1 , m i d ] [1, mid] [1,mid] 和 [ m i d + 1 , r ] [mid+1, r] [mid+1,r] 中。显然一个点对仅有第一次跨越mid是被统计(即第一次跨越之前,两点在一边;第一次跨越之后,两点不会出现在同一区间中)。
一般顺序,可以用于偏序计数问题:
void cdq(int l, int r){
if(l==r) return ;
int mid=(l+r)/2;
cdq(l, mid); cdq(mid+1, r);
work...
}
例题:
P3810 【模板】三维偏序(陌上花开)
P4169 [Violet]天使玩偶/SJY摆棋子
对于偏序计数的常见trick,有加入时间维离线,可以解决较为简单的动态问题。
例题:
P3157 [CQOI2011]动态逆序对
在cdq辅助转移偏序dp时,顺序会变为
void cdq(int l, int r){
if(l==r) return ;
int mid=(l+r)/2;
cdq(l, mid);
work...
cdq(mid+1, r);
}
考虑在dag模型类dp过程中,用来转移的状态应是转移完全完成的。这种转移可以保证用 [ 1 , m i d ] [1, mid] [1,mid] 中的值更新 [ m i d + 1 , r ] [mid+1, r] [mid+1,r] 时, [ 1 , m i d ] [1, mid] [1,mid] 的值更新完备。而上述的“一般转移”则不行,因为在其递归 [ m i d + 1 , r ] [mid+1, r] [mid+1,r] 时, [ 1 , m i d ] [1, mid] [1,mid] 到 [ m i d + 1 , r ] [mid+1, r] [mid+1,r] 的转移尚未完成。当然,如果更新方向相反,cdq分治的顺序也要调整反过来。
例题:
P2487 [SDOI2011]拦截导弹
P4093 [HEOI2016/TJOI2016]序列
cdq同样可以用于带修问题中。具体的,将修改和询问都加入时间维后放在一个序列中,仿照上文的trick将时间维cdq掉。我们需要考虑的就是每一个 修改-询问 的点对中修改对询问的影响。这需要修改具有可叠加性,即前面的修改需要被后面的修改覆盖时易取消。
总结:
正常处理k维偏序相关的题目时,对一维扫描,则余下k-1维必须使用数据结构动态维护。而cdq分治对于第一维采用分治处理,消去了第一维的影响,故可以扫描第二维,余下k-2维使用数据结构。
而cdq的好处是,在通常最高是三维的问题中,二维数据结构的空间复杂度普遍在 O ( n l o g n ) O(nlogn) O(nlogn),而cdq仅需一维数据结构支持,空间复杂度仅为 O ( n ) O(n) O(n)。
整体二分
针对一类包含多组询问,单独的一个询问可以通过在答案值域上二分解决的题目的算法。通过遍历二分答案的过程中可能产生的值域(可以想象成答案值域形成的线段树上的全体区间),一次性确定所有询问的答案。可以发现其求得答案的顺序和询问顺序无关(事实上是按答案值域递增的顺序),故其为离线算法。
分析节省时间复杂度的原理。我们设答案值域大小为 N N N,询问组数为 M M M,在将值域上一个点计入二分统计中的复杂度为 O ( S ) O(S) O(S),二分中一次check的查询的复杂度为 O ( C ) O(C) O(C)。若单独做一组询问,复杂度为 O ( C l o g N + N S ) O(ClogN+NS) O(ClogN+NS)。顺序回答问题,总复杂度为 O ( M C l o g N + M N S ) O(MClogN+MNS) O(MClogN+MNS)。对于整体二分,每个值域上的点只会被插入 l o g N logN logN 次。总复杂度变为 O ( M C l o g N + N S l o g N ) O(MClogN+NSlogN) O(MClogN+NSlogN)。可以发现整体二分对于一个值域区间在计入一次后,对所有答案包含在此区间中的询问统一check,节省了原来的大量重复计入。
题目示例:
P3332 [ZJOI2013]K大数查询
P3834 【模板】可持久化线段树 2
线段树分治
一种解决一类特殊带修问题的算法。
一个问题需要使用线段树分治,一般有以下特点:
1.有多组修改,每个修改的影响持续一个时间段
[
l
,
r
]
[l, r]
[l,r],即在
[
l
,
r
]
[l, r]
[l,r] 时间段以内修改生效,此外的时候无效。亦可以表现为需要支持修改撤销。
2.修改操作的复合满足交换律(即与顺序无关)。
3.对于可行的维护方法,修改操作在与其他修改复合过后难以撤销。可以理解为只容易撤销末尾的操作(沿着操作栈撤销)。
4.多组询问,每次询问的时间不同。
我们考虑将时间轴建成线段树,每一个操作对应的时间段可以在对应 l o g log log 个区间。将每个操作拆分后丢到线段树上的对应区间中去,询问丢到对应叶子节点上,然后我们遍历线段树,并保证仅进行了从根到当前点的路径中包含的修改操作(即维护到根的操作链,在返回父亲时只需要撤回自己节点上的操作即可)。到叶子节点时回答询问。
题目示例:
P9168 [省选联考 2023] 人员调度