CDQ(陈丹琦)分治
CDQ显然是一个人的名字(2008NOI金牌选手陈丹琦) 这种离线的分治算法在算法界被称为"CDQ分治"。
首先回忆一下归并排序的分治, 它的操作是将数组二分, 然后分别对左半部分和右半部分递归的用归并排序, 左右两部分都有序后再将两个部分合并成一个有序的数组, 这是大家都十分熟悉的分治.
而CDQ(陈丹琦)分治的不同之处在于,每次划分后将左半部分对右半部分的贡献的累加, 递归的调用这个过程, 实现整体结果的计算。具体什么是前半部分对后半部分的贡献需要根据题意而定.
要用CDQ分治解决问题, 首先要求是题目支持离线操作,如果题目强制在线的话就不能用CDQ分治了,只能考虑用别的数据结构来维护
所以CDQ分治的的主要步骤为
1.先对整体排序(按照第一维排序);
2.计算左半区间对右半区间的贡献;
3.撤销累加贡献时的操作;
4.二分的对左半部分和右半部分调用这个过程;
下面来看一个具体的例子来加深对CDQ分治的了解
BZOJ P2683 简单题
Description
你有一个N*N的棋盘,每个格子内有一个整数,初始时的时候全部为0,现在需要维护两种操作:
Sample Input
4
1 2 3 3
2 1 1 3 3
1 2 2 2
2 2 2 3 4
3Sample Output
3
5
根据题意,这题有两个要素:一个是x坐标, 一个是y坐标, 我们很容易想到第一维x直接排序, 第二维y用数据结构维护, 这里可以用树状数组(对于树状数组默认大家都会,在此不做过多介绍)维护好x和y的序列后,现在要解决的问题就是操作顺序如何划分:因为只有添加操作和查询操作按照一定的顺序进行分析才能得到正确的结果,每一次查询都需要确保前面的添加操作已经完成,否则可能得到的就是错误答案。那么如何维护操作的顺序呢?
我们不妨添加一个维度(第三维:时间)。对每一个操作都记录下他的时间(或者说顺序)。这样就能区分哪些操作在哪些操作的前面了。如何维护第三维呢?这里我们就可以用CDQ分治来解决。
在运用CDQ分治前要搞清楚一个问题,在本题中,什么叫前半部分对后半部分的贡献呢? 记ADD操作为添加,Q操作为查询, 考虑这样一组操作 :ADD(3,3,1) Q(4,5) Q(2,5) ADD(1,2,1) 在这组操作中:
- ADD(3,3,1)可以对Q(4,5)的结果产生影响,但是却不能对Q(2,5)的结果产生影响, 因为根据题意, ( 2 , 5 ) (2,5) (2,5)对于现有的点 ( 3 , 3 ) (3,3) (3,3)不满足 ( 2 > = 3 ) (2 >= 3) (2>=3)
- ADD(1,2,1)不能对Q(4,5) 或Q(2,5)的结果产生影响, 因为此添加操作在上述查询操作之后
这就是CDQ分治中的更新前半部分对后半部分的影响, 对于x和y我们已经用排序和树状数组维护好了,只需要对第三维时间进行CDQ分治即可
下面附上代码:
/*
* Copyright (c) 2019 Ng Kimbing, HNU, All rights reserved. May not be used, modified, or copied without permission.
* @Author: Ng Kimbing, HNU.
* @LastModified:2019-04-17 T 11:29:42.681 +08:00
*/
package ACMProblems.DivideAndConquer;
import java.util.Arrays;
import static ACMProblems.ACMIO.*; // My IO
public class EasyProblem_2683 {
static class Operation {
int x;
int y;
int operationType;
int n_thQuery;
int timeLine;
int value;
Operation(int x, int y, int operationType, int n_thQuery, int timeLine, int value) {
this.x = x;
this.y = y;
this.operationType = operationType;
this.n_thQuery = n_thQuery