hdu 5126 stars ( CDQ分治 + 树状数组)

时间:2021-06-29 21:47:23

被一个叫做旺仔的坏学长教会的算法---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;
}