CDQ分治
CDQ分治,又称基于时间的分治算法,常用于解决多维偏序问题。该算法可以通过增加log(n)的代价将偏序问题降掉一维,从而转化成更易解决的多维偏序问题。事实上,CDQ分治能解决的题目很多都可以用支持动态查询的高级数据结构完成,但是CDQ分治的思维难度和代码实现难度较于高级数据结构减小了很多,并且空间更小。
有些题目要求诸如:只有修改操作的属性值小于(或大于)某一询问操作的答案,该修改操作才能对询问产生影响。这里,我们视操作顺序为第一维,属性值为第二维,修改/询问的值为第三维,发现这就是三维偏序问题。
一、大致思路
我们拿逆序对这个二维偏序问题开始说。对于逆序对,我们可以分治排序,也可以先排序再用一个树状数组求出来。那么如果我们能用一种手段把三维偏序问题降维,就可以很容易地解决了。那么怎么降维?答案是,用分治!
于是我们的重点就转化称如何用分治降维。
值得一提的是,CDQ分治大致有两个基本形式:多维偏序降维 和 动态修改转换成静态修改统一查询。只要问题能转化成这两个形式,那就可以考虑用CDQ分治了。
二、算法简介
CDQ分治适用于满足这两个条件的数据结构题:
- 修改之间对于答案的贡献是独立的,修改操作之间互不影响修改效果
- 题目可离线
我们将整个操作序列[l,r]等分成前一半操作序列(后文简称前序列)[l,mid]和后一半操作序列(简称后序列)[mid+1,r],可以发现这样两个性质:
-
后序列中修改操作不会对前序列中操作序列产生影响
-
后序列中每一个询问x仅受两部分影响:[l,mid]中的修改操作,[mid+1,r]中在x之前的修改。
而事实上,第二条性质中的第2个问题在分治过程中必定会转化成第1个问题,因为总会有一个分界线使得x与前面所有修改操作分列前后序列。那么我们只需要解决第1个问题就行了。
而对于第一条性质,我们可以发现,前序列中所有的修改操作都在后序列的询问操作之前。也就是说,对于后序列,前序列中的修改操作可以任意调换顺序。这样一来,我们可以将前一部分的修改全部做完再回答后一部分的询问
关于第二条性质,实际上并不是所有问题都满足该性质。但是,由于思维方式的一致,我们对于”后序列对前序列也有影响“问题的求解并不赘述,基本流程和实现没有太大差别,注意灵活变通。
三、详解分治过程
首先,我们按照开头说的那样,将问题转化成三维偏序问题。我们将问题按照第一维排序,这样重点转向二、三维。我们以属性值为第二维进行归并排序,这样前序列和后序列内部第二维都是有序的。随后用类似归并排序的方式将两个序列合并成一个新的第二维有序的序列,在这个过程中回答 后序列中的询问。
于是,整个问题就转化成了离线的、与原问题同样规模的“一开始给出所有修改”然后“回答若干询问”的更简单的问题。设 解决无动态修改操作的原问题 的复杂度为f(n),则总复杂度为O(f(n)log(n))
四、例题
P3810 【模板】三维偏序(陌上花开)
来源
模板题
自认为我的码风已经变得温和很多了
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define re register
#define lowbit(x) (-(x)&(x))
il int read(){
int s=0,w=1;char c=getchar();
while(c<'0'||c>'9'){ if(c=='-') w=-1;c=getchar();}
while(c>='0'&&c<='9'){ s=(s<<1)+(s<<3)+c-'0';c=getchar();}
return s*w;
}
const int N=2e5+10;
int n,K,f[N],ans[N],cnt;
struct node{ int x,y,z,id,sz;}a[N],q[N];
il bool operator==(const node &c,const node &d){
return c.x==d.x && c.y==d.y && c.z==d.z;
}
namespace FW{//Fenwick-Tree
int tr[N];
il void upd(int x,int val){
while(x<=K) tr[x]+=val,x+=lowbit(x);
}
il int getsum(int x){
int res=0;
while(x>0) res+=tr[x],x-=lowbit(x);
return res;
}
} using namespace FW;
il bool cmp(node c,node d){
if(c.x!=d.x) return c.x<d.x;
if(c.y!=d.y) return c.y<d.y;
return c.z<d.z;
}
il void CDQ(int l,int r){
if(l==r) return;
int mid=l+r>>1;
CDQ(l,mid),CDQ(mid+1,r);
int p1=l,p2=mid+1,p=l;
while(p1<=mid && p2<=r){
if(a[p1].y<=a[p2].y) q[p]=a[p1],upd(a[p1].z,a[p1].sz),++p1,++p;
else q[p]=a[p2],f[a[p2].id]+=getsum(a[p2].z),++p2,++p;
}
while(p1<=mid) q[p]=a[p1],upd(a[p1].z,a[p1].sz),++p1,++p;
while(p2<=r) q[p]=a[p2],f[a[p2].id]+=getsum(a[p2].z),++p2,++p;
for(re int i=l;i<=mid;i++) upd(a[i].z,-a[i].sz),a[i]=q[i];
for(re int i=mid+1;i<=r;i++) a[i]=q[i];
}
int main()
{
n=read(),K=read();
for(re int i=1;i<=n;i++) q[i]=(node){read(),read(),read(),i,0};
sort(q+1,q+1+n,cmp);
for(re int i=1;i<=n;i++){
if(q[i]==q[i-1]) a[cnt].sz++;
else a[++cnt]=q[i],a[cnt].id=cnt,a[cnt].sz=1;
}
CDQ(1,cnt);
for(re int i=1;i<=cnt;i++) f[a[i].id]+=a[i].sz-1,ans[f[a[i].id]]+=a[i].sz;
for(re int i=0;i<n;i++) printf("%d\n",ans[i]);
return 0;
}
end