我终于学会了充电器分治
陈丹琦分治是由陈丹琦发明整理的,一种用来解决三维偏序问题的算法。(下文为打字方便,简写为充电器分治。)
那么什么是偏序问题呢?
一维偏序:给定一个序列 \(a\),求出对于每个 \(i\),\(j\ne i\) 且 \(a_j< a_i\) 的 \(j\) 的个数。
二维偏序:给定两个序列 \(a,b\),求出对于每个 \(i\),\(j\ne i,a_j< a_i,b_j< b_i\) 的 \(j\) 的个数。
三维偏序:给定三个序列 \(a,b,c\),求出对于每个 \(i\),\(j\ne i,a_j< a_i,b_j< b_i,c_j< c_i\) 的 \(j\) 的个数。
以此类推还有四维偏序,五维偏序等高维偏序。
我们依次探讨。
一维偏序
我们先把每个 \(i\) 按照 \(a_i\) 排个序。
然后探讨完了。
二维偏序
我们先把每个 \(i\) 按照 \(a_i\) 为第一关键字,\(b_i\) 为第二关键字排个序。
可以发现所有的在 \(i\) 之前的都满足 \(a_j< a_i\)。
那我们就要求在 \(i\) 之前的 \(b_j<b_i\) 的数量。
第一种做法:
我们开个桶,每到一个位置就记录一下权值。
最后的答案就是 \(b_i\) 的前缀和。
单点修改单点查询?树状数组!
第二种做法:
归并排序求逆序对大家都学过。
只要在求逆序对的时候记录一下是哪个 \(i\) 产生的答案就行了。
三维偏序
我们发现只要把「树状数组求二维偏序」和「归并求二维偏序」合体就能做三维偏序了。
恭喜你发明了充电器分治。
具体而言:
- 我们先按 \(a_i\) 排序,去掉第一维。
- 然后分治,消去第二维。
- 最后树状数组记录第三维信息。
可能不太对。但反正我理解的就这么简单。
分治部分的代码(以陌上花开为例):
void merge_sort(int l,int r){
if(l>=r) return;
int mid=((l+r)>>1);
merge_sort(l,mid);
merge_sort(mid+1,r);
int j=l,i=mid+1,k=l;
while(i<=r&&j<=mid){
if(p[j].b<=p[i].b) add(p[j].c,p[j].s),q[k++]=p[j++];
else p[i].f+=sum(p[i].c),q[k++]=p[i++];
}
while(j<=mid) add(p[j].c,p[j].s),q[k++]=p[j++];
while(i<=r) p[i].f+=sum(p[i].c),q[k++]=p[i++];
for(j=l;j<=mid;j++) add(p[j].c,-p[j].s);
for(i=l;i<=r;i++) p[i]=q[i];
}
例题
[CQOI2011] 动态逆序对
若 \(t\) 为删去时间,\(pos\) 为在序列中的原位置,\(val\) 为权值,则问题转化为对于每个 \(i\) 求满足
的 \(j\) 的个数。这个答案就是删除 \(i\) 元素后,消失的逆序对个数。
因此我们跑两遍充电器即可。
[HEOI2016/TJOI2016] 序列
待会写。

浙公网安备 33010602011771号