==== €€£ WARNING ====
这篇博文由于过于久远并没有什么干货已被废弃
新博文链接->CDQ分治&整体二分
==== ====
最近学了一种叫做CDQ分治的东西...用于离线处理一系列操作与查询似乎跑得很快233
CDQ的名称似乎源于金牌选手陈丹琦
概述:
对于一坨操作和询问,分成两半,单独处理左半边和处理左半边对于右半边的影响,就叫$CDQ$分治。
乍一看似乎不算难理解...?
这"一坨操作和询问"是要求靠左的操作可以影响所有右侧操作,靠右的查询的值依赖于左侧的操作...
内部实现:
将左右区间按一定规律排序后分开处理,递归到底时直接计算答案,对于一个区间,按照第二关键字split成两个区间,先处理左区间,之后因为整个区间是有序的,就可以根据左区间来推算右区间的答案,最后递归处理右区间即可。归纳起来就是
1. 区间按第一关键字分成两半
2. 计算左区间对右区间的贡献
3. 撤销修改
4. 按第二关键字拆成两半并排序
5. 递归下去继续处理
这种时候常常只能选择参考代码感性理解一下(雾)
对于上面的例题($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 ext{~}6$行没啥可讲的(吧)
然后我们从$l$到$r$进行遍历.因为我们按$x$和$y$排序了所以更改肯定在它所影响的查询操作的值计算之前进行($7 ext{~}12$行)
接着我们撤销修改,因为递归下去后这些值会失效所以要清空($13 ext{~}16$行)
然后我们按照时间戳分别分为左右两部分($17 ext{~}24$行)
最后递归下去处理.
仔细分析我们可以发现这个过程刚好做到了"不重不漏":不重复计算查询涉及到的修改操作的贡献;不漏掉修改操作对后面查询的贡献.
然后摘一句总结
CDQ分治主要用于处理能够离线的低维偏序(3维及以下),3维偏序常态是套BIT,一般对于x排序,然后对于id进行cdq分治,对于y用BIT来维护即可。
据说更高维的偏序也可以处理但是好像需要一些特殊方法?
(然后又草率地结束了)
(我好菜啊QwQ)
(放图跑)