首页 > 代码库 > [学习笔记] CDQ分治

[学习笔记] CDQ分治

最近学了一种叫做CDQ分治的东西...用于离线处理一系列操作与查询似乎跑得很快233

CDQ的名称似乎源于金牌选手陈丹琦

概述:

对于一坨操作和询问,分成两半,单独处理左半边和处理左半边对于右半边的影响,就叫$CDQ$分治。 

乍一看似乎不算难理解...?

这"一坨操作和询问"是要求靠左的操作可以影响所有右侧操作,靠右的查询的值依赖于左侧的操作...

内部实现:

将左右区间按一定规律排序后分开处理,递归到底时直接计算答案,对于一个区间,按照第二关键字split成两个区间,先处理左区间,之后因为整个区间是有序的,就可以根据左区间来推算右区间的答案,最后递归处理右区间即可。归纳起来就是 
1. 区间按第一关键字分成两半
2. 计算左区间对右区间的贡献 
3. 撤销修改
4. 按第二关键字拆成两半并排序
5. 递归下去继续处理

这种时候常常只能选择参考代码感性理解一下(雾)

例题-Mokia

对于上面的例题($Mokia$)来说,我们先将一个查询操作差分为$4$个前缀和分别计算,然后将所有操作按$x$值为第一关键字,$y$为第二关键字排序(保证左边的操作可以影响右边操作),然后按照时间戳分开重新排序为两个序列并分开递归下去求值QwQ

直接拿这个板子题的实现细节举个栗子好了QwQ

我们拿$CDQ$函数来说一下...

 

 1 void CDQ(int l,int r){
 2     if(l==r)
 3         return;
 4     int mid=(l+r)>>1;
 5     int ll=l;
 6     int lr=mid+1;
 7     for(int i=l;i<=r;i++){
 8         if(query[i].ID<=mid&&query[i].operation==0)
 9             Add(query[i].y,query[i].value);
10         if(query[i].ID>mid&&query[i].operation==1)
11             ans[query[i].position]+=query[i].value*Query(query[i].y);
12     }
13     for(int i=l;i<=r;i++){
14         if(query[i].ID<=mid&&query[i].operation==0)
15             Add(query[i].y,-query[i].value);
16     }
17     for(int i=l;i<=r;i++){
18         if(query[i].ID<=mid)
19             tmp[ll++]=query[i];
20         else
21             tmp[lr++]=query[i];
22     }
23     for(int i=l;i<=r;i++)
24         query[i]=tmp[i];
25     CDQ(l,mid);
26     CDQ(mid+1,r);
27 }

 

$2\text{~}6$行没啥可讲的(吧)

然后我们从$l$到$r$进行遍历.因为我们按$x$和$y$排序了所以更改肯定在它所影响的查询操作的值计算之前进行($7\text{~}12$行)

接着我们撤销修改,因为递归下去后这些值会失效所以要清空($13\text{~}16$行)

然后我们按照时间戳分别分为左右两部分($17\text{~}24$行)

最后递归下去处理.

仔细分析我们可以发现这个过程刚好做到了"不重不漏":不重复计算查询涉及到的修改操作的贡献;不漏掉修改操作对后面查询的贡献.

然后摘一句总结

CDQ分治主要用于处理能够离线的低维偏序(3维及以下),3维偏序常态是套BIT,一般对于x排序,然后对于id进行cdq分治,对于y用BIT来维护即可。 

 据说更高维的偏序也可以处理但是好像需要一些特殊方法?

(然后又草率地结束了)

(我好菜啊QwQ)

(放图跑)

技术分享

 

[学习笔记] CDQ分治