CDQ分治是一种神奇的分治算法
它的核心思想大致是这样的
将所有的操作看成一个区间
一个[L,R]的区间分为[L,mid][mid+1,R]这两个子问题
然后处理[L,mid]这个区间对[mid+1,R]这个区间的贡献
这样讲解十分抽象,我们还是来看几个实际问题
二维偏序问题:
给定N个有序对(a,b),求对于每个(a,b),满足a2<a且b2<b的有序对(a2,b2)有多少个
我们现将所有点排序按一维,这样右区间就不会对左区间产生影响,就可以CDQ了
在具体处理左边区间对右边影响时,
我们将[L,mid]和[mid+1,R]再分别按另一维排序
然后用两个指针扫一下,就好了
我们设想一下将二维的偏序加到三维:
其实做法是差不多的
一维排序,一维CDQ一维树状数组
具体处理时我们将[L,min],[mid+1,R]排序后用两个指针扫时
遇到左边小于右边的情况就将树状数组在a[i].z处加上1
然后在j处的答案就是sum(a[j].z)
上代码(洛谷3810)
# include<iostream> # include<cstdio> # include<algorithm> # include<cmath> # include<cstring> using std::sort; inline int read() { int x=0; char ch=getchar(); while(ch>'9' || ch<'0') ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return x; } const int mn = 100005; struct element{ int x,y,z,s,ans; }; element a[mn],b[mn]; int tot,ans[mn]; bool cmp1(element m,element n)//按x排序 { if(m.x==n.x && m.y==n.y) return m.z<n.z; else if(m.x==n.x) return m.y<n.y; else return m.x<n.x; } bool cmp2(element m,element n)//按y排序 { if(m.y==n.y) return m.z<n.z; return m.y<n.y; } int n,k; struct BIT{ int siz,tr[mn*2]; void add(int i,int x) { for(i;i<=siz;i+=(i&-i)) tr[i]+=x; } int sum(int i) { int ret=0; for(i;i;i-=(i&-i)) ret+=tr[i]; return ret; } }T; void CDQ(int l,int r) { if(l==r) return ; int mid=l+r>>1; CDQ(l,mid); CDQ(mid+1,r); sort(b+l,b+mid+1,cmp2); sort(b+mid+1,b+r+1,cmp2); int i=l,j=mid+1; while(j<=r) { while(i<=mid && b[i].y<=b[j].y) { T.add(b[i].z,b[i].s); i++; } b[j].ans+=T.sum(b[j].z); j++; } for(int s=l;s<i;s++) T.add(b[s].z,-b[s].s); } int main() { n=read(),k=read(); T.siz=k; for(int i=1;i<=n;i++) a[i].x=read(),a[i].y=read(),a[i].z=read(); sort(a+1,a+1+n,cmp1); int cnt=0; for(int i=1;i<=n;i++) { cnt++; if(a[i].x!=a[i+1].x || a[i].y!=a[i+1].y || a[i].z!=a[i+1].z) { b[++tot]=a[i]; b[tot].s=cnt; cnt=0; } } CDQ(1,tot); /*for(int i=1;i<=tot;i++) printf("%d ",b[i].ans); printf("\n");*/ for(int i=1;i<=tot;i++) ans[b[i].ans+b[i].s-1]+=b[i].s; for(int i=0;i<n;i++) printf("%d\n",ans[i]); return 0; }
(未完待更)
来源:https://www.cnblogs.com/logeadd/p/9305687.html