被一个叫做旺仔的坏学长教会的算法---CDQ分治,和归并排序很像,只是在归并排序的过程中对每个查询进行统计。
下面这个是他的博客链接:
我主要结合hdu 5126这个题来讲解CDQ分治的具体做法:
题目链接:
题目大意:
在一个三维空间当中,每次进行一个操作,添加一个点或者统计空间中的某一个长方体范围内的所有点
题目分析:
就是用CDQ分治就离线的统计出每个查询的结果
首先我们将每个点z坐标离散化,那么我们就可以利用树状数组统计出每个查询点之前插入的点的z坐标小于等于当前查询的点的个数。
那么我们就是要先将x,y序不大于当前查询,且时序小于当前查询的点先加入的树状数组当中,
那么这一步操作我们需要进行一个嵌套的CDQ分治:
1.既然是分治,那么一定要先对区间进行划分,采用最常用的二分的方法进行划分,然后递归的进行划分
2.当分治到段的大小为1的时候开始在回溯的过程中进行归并
3.首先我们能够保证之前的每一段内的按照y坐标升序排序,且左半段的时序均小于右半段的时序,而且每段内部的查询针对于当前段内部的点已经统计过,那么对于左半段因为统计结果不会和比他时序大的查询点有关系,所以已经是当前段的最终结果,而右半段因为左半段的时序都比他小,所以只要是y坐标比他小的,就能会对右半段中的点的统计结果有影响。
4.那么我们就将左半段的数据点和右半段的查询点放入一个新的数组,在这个数组当中,能够保证y的升序,且数据点和查询点分别保证了时序的升序
5.那么我们对这个新构建的数组再做一次CDQ分治,也就是再对它进行划分,递归的划分
6.当新的数组划分为1的时候,进行归并,在归并的过程中,左半段的数据点时序一定小于右半段的查询点,且左半段的y坐标一定不大于右半段的查询点,那么我们就是在归并的过程按照x坐标进行,归并,左半段内部保证按照x坐标有序,右半段也是按照x坐标有序,所以说对于左半段的数据点要加入到树状数组当中,对于右半段中的查询点统计之前树状数组中插入的z坐标不大于当前查询点z坐标的全部的值。
7.然后继续归并,一致维护数组的性质即可,在这一过程中,在第二次CDQ分治中,因为数组中凡是存在于查询点之前的数据点均保证了y,时序均在查询点之前,所以在对x序进行归并的时候,右半部的查询点只要x坐标大于左半部的数据点就能够满足x,y时序均小于查询点,在加上树状数组的操作,就能统计出比当前查询点x,y,z时序均小的点的个数了
8.然后对于每个查询的切割,就是利用长方体,利用切补法转化为多个前缀和的查询,然后统计一下就好了
代码如下:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define MAX 450007
using namespace std;
//用来存储星星和每次查询拆分出的点的数据结构
struct Node
{
int x,y,z,p,t;
Node(){}
Node( int x , int y , int z , int p , int t )
:x(x),y(y),z(z),p(p),t(t){}
};
Node star[MAX];
Node star2[MAX];
Node star3[MAX];
int tree[MAX];
int lowbit ( int x )
{
return x&-x;
}
void add ( int i ,int x )
{
while ( i < MAX )
{
tree[i] += x;
i += lowbit(i);
}
}
int get ( int i )
{
int ans = 0;
while ( i )
{
ans += tree[i];
i -= lowbit ( i );
}
return ans;
}
int res[MAX];
//CDQ针对于y坐标上的分治
void CDQ2 ( int l , int r )
{
if ( l == r ) return;//当前分段情况下仅有一个元素时返回
int mid = (l+r)/2;//将当前处理的段进行分段
CDQ2( l , mid );//对前半段进行分治处理
CDQ2( mid+1 , r );//对后半段进行分治处理
//因为分治操作保证了前半段和后半段已经维护好,也就是前半段中的元素以y为关键字是有序的
//后半段的元素以y为关键字同样也是有序的
//所以我们可以采取类似于归并排序的方式将两个段归并,并进行处理
int l1 = l , r1 = mid+1;
while ( r1 <= r )
{
//左侧中所有的点的时序均小于右侧的,所以右侧的查询会包含左侧的y坐标比x坐标小的点
//将这些点加入到树状数组中,那么我们就可以查询到这些点中中z坐标比当前查询小的
while ( l1 <= mid && star2[l1].y <= star2[r1].y )
{
if ( star2[l1].t == 0 ) add ( star2[l1].z , 1 );
l1++;
}
//如果当前是一个查询,那么统计左侧有多少个在它查询范围内的点
if ( star2[r1].t != 0 )
{
res[star2[r1].p] += get ( star2[r1].z ) * star2[r1].t;
}
r1++;
}
//将树状数组还原
while ( l1 > l )
{
--l1;
if ( star2[l1].t == 0 ) add ( star2[l1].z , -1 );
}
l1 = l , r1 = mid+1;
//将两个段的元素归并,保持原有的性质
for ( int i = l ; i <= r ; i++ )
if ( (l1 <= mid && star2[l1].y <= star2[r1].y) || r1 > r )
star3[i] = star2[l1++];
else star3[i] = star2[r1++];
for ( int i = l ; i <= r ; i++ )
star2[i] = star3[i];
}
//CDQ针对于X坐标上的分治
void CDQ1 ( int l , int r )
{
if ( l == r ) return;
int mid = (l+r)/2;//将当前段分成两段
CDQ1(l,mid);//对第一段进行分治
CDQ1(mid+1,r);//对第二段进行分治
int l1 = l , r1 = mid+1 , n = 0;
while ( r1 <= r )
{
//左侧的查询在之前的分治过程中已经统计完毕,所以跳过
while ( star[l1].t != 0 && l1 <= mid ) l1++;
//右侧中的元素已经被右侧的查询已经被统计过,所以跳过
while ( star[r1].t == 0 && r1 <= r ) r1++;
if ( r1 > r ) break;
//构造出需要统计的star2数组,继续进行分治统计
if ( (star[l1].x <= star[r1].x && l1 <= mid ) || r1 > r )
star2[n++] = star[l1++];
else star2[n++] = star[r1++];
}
if ( n > 0 ) CDQ2(0,n-1);
l1 = l , r1 = mid+1;
//归并当前数组当中的结果
for ( int i = l ; i <= r ; i++ )
{
if ( (star[l1].x <= star[r1].x && l1 <= mid )|| r1 > r )
star3[i] = star[l1++];
else star3[i] = star[r1++];
}
for ( int i = l ; i <= r ; i++ )
star[i] = star3[i];
}
int compn ( Node a, Node b )
{
return a.p < b.p;
}
int compz ( Node a , Node b )
{
return a.z < b.z;
}
int main ( )
{
int t,q,a,x1,y1,z1,x2,y2,z2;
scanf ( "%d" , &t );
while ( t-- )
{
int n = 0;
scanf ( "%d" , &q );
while ( q-- )
{
scanf ( "%d" , &a );
if ( a == 1 )
{
scanf ( "%d%d%d" , &x1 , &y1 , &z1 );
star[n++] = Node ( x1 , y1 , z1 , n , 0 );
}
else
{
scanf ( "%d%d%d%d%d%d" , &x1 , &y1 , &z1 , &x2 , &y2 , &z2 );
star[n++] = Node ( x2 , y2 , z2 , n , 1 );
star[n++] = Node ( x2 , y1-1 , z2 , n , -1 );
star[n++] = Node ( x1-1 , y2 , z2 , n , -1 );
star[n++] = Node ( x2 , y2 , z1-1 , n , -1 );
star[n++] = Node ( x1-1 , y1-1 , z2 , n , 1 );
star[n++] = Node ( x1-1 , y2 , z1-1 , n , 1 );
star[n++] = Node ( x2 , y1-1 , z1-1 , n , 1 );
star[n++] = Node ( x1-1 , y1-1 , z1-1 , n , -1 );
}
}
memset ( res , 0 , sizeof ( res ));
sort ( star , star+n , compz );
a = 1;
star[n].z = -1;
for ( int i = 0 ; i < n ; i++ )
if ( star[i].z != star[i+1].z )
star[i].z = a++;
else star[i].z = a;
sort ( star , star+n , compn );
CDQ1(0,n-1);
sort ( star , star+n , compn );
for ( int i = 0 ; i < n ; i++ )
{
if ( star[i].t != 0 )
{
int ans = 0;
for ( int j = 0 ; j < 8 ; j++ )
ans += res[i+j];
printf ( "%d\n" , ans );
i += 7;
}
}
}
return 0;
}