我终于学会了充电器分治

陈丹琦分治是由陈丹琦发明整理的,一种用来解决三维偏序问题的算法。(下文为打字方便,简写为充电器分治。)

那么什么是偏序问题呢?

一维偏序:给定一个序列 \(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\) 的前缀和。

单点修改单点查询?树状数组!

第二种做法:

归并排序求逆序对大家都学过。

Link:逆序对之史诗

只要在求逆序对的时候记录一下是哪个 \(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\) 求满足

\[(t_i<t_j,pos_i<pos_j,val_i>val_j) \lor (t_i<t_j,pos_i>pos_j,val_i<val_j) \]

\(j\) 的个数。这个答案就是删除 \(i\) 元素后,消失的逆序对个数。

因此我们跑两遍充电器即可。

[HEOI2016/TJOI2016] 序列

待会写。

posted @ 2025-12-06 16:38  NoInt_Young  阅读(7)  评论(0)    收藏  举报