题解 P3755 【[CQOI2017]老C的任务】

· · 题解

题意简述:

平面内有N个点,每个点有一个值Pi和坐标(xi,yi),有M次查询,查询以(x1,y1)和(x2,y2)为两对角顶点的矩形内部点的值之和。

Solution:

本题做法很多:

CDQ分治,KD-tree等算法和数据结构可以解决本题。

这里介绍一种更加通俗易懂的解法,分块,借鉴了洛谷中@最后一颗星辰 的题解。

不过我觉得那篇题解略微简述,在此作详细的补充和解释。

对于暴力算法,二维前缀和可以解决部分数据,但是N,M太大,数组开不下,我们只能考虑另外的解法。

首先是预处理,先以所有点的x坐标为第一关键字排序,按照数列分块的老套路,把每sqrt(N)个点分为一块,共sqrt(N)+1块。

这时候用数组存下每块的点,统计功率的前缀和,保存下来,具体地:

我们将数组去重,这样方便进行查找,因为只需要看排序后的x坐标数组,若求出xi在排序后数组的某个位置px,那么px/t1(t1是每块的长度)便是属于第几块了,这是一个显然的事情,可以配合代码理解!

    n=read();ask=read();
    t1=sqrt(n);t2=n/t1+1;
    for(ll i=1;i<=n;i++)
    {
          xx[i]=read();yy[i]=read();zz[i]=read();
          dx[i]=xx[i];
    }
    sort(dx+1,dx+n+1);
    ll tx=unique(dx+1,dx+n+1)-dx-1;//去重后的数组长度 
    for(ll i=1;i<=n;i++)
    {
       ll px=lower_bound(dx+1,dx+tx+1,xx[i])-dx;//大于等于xx[i]的第一个位置(这里实际上就是查找了xx[i]的位置,因为必然存在)
       ll Z=px/t1+1;        
       a[Z][++js[Z]].x=xx[i];
       a[Z][js[Z]].y=yy[i];
       a[Z][js[Z]].p=zz[i];
    }
    for(ll i=1;i<=t2;i++)
    {
       sort(a[i]+1,a[i]+js[i]+1,cmp2);
       for(ll j=1;j<=js[i];j++)
       {
          sum[i][j]=sum[i][j-1]+a[i][j].p;//处理每块的基站功率的前缀和,sum[i][j]表示第i块前j个基站功率的和(方便统计答案) 
          b[i][j]=a[i][j].y;
       }
    }

再来看查询,对于一个矩形,可以先计算中间的部分,再计算旁边的部分。

为什么会有“中间”和“旁边”之分呢?我们对点数组进行分块的时候,对于一个给定的矩形,可能会有点没有在一个完整块中,这时候就需要我们再次统计这些点。

具体地:对于两端的点,枚举块内每个点,判断是否在矩形内部,如果在则统计答案。

对于中间的点,二分查找左右界点,二分查找上下界点(这里有很多细节需要注意)。

计算出边界后加上之前统计的每块功率前缀和即可。

该算法的时间复杂度为:O(Nsqrt(n)log2(n)),优化常数可以AC此题。

易错点:

二分查找时,边界的取或者不取需要特别注意!!!

要判断点是否是同一块,若不是,由于前面计算了块p的贡献,再计算一次块q的贡献(同一块无需再计算)。

好了,这道直奔主题的分块题就到此结束了,注释在代码里写得比较清楚。

#include<bits/stdc++.h>
#define N 100010
#define Q 1005
#define ll long long
using namespace std;
struct M
{
    ll x,y,p;
}a[Q][Q];//a[i][j]:分块后的第i块的第j个数,num[i](js[i])表示第i块的数的个数(这里看好久才看出来)
ll ask,dx[N],xx[N],yy[N],zz[N],t1,t2,a1,a2,b1,b2,n,js[N],l[N],r[N],sum[Q][Q],b[Q][Q];//t1:块数   t2:块的长度   sum:一个块的前缀功率之和 
ll cmp1(M A,M B)
{
    if(A.x<B.x) return 1;
    else return 0;
}
ll cmp2(M A,M B)
{
    if(A.y<B.y) return 1;
    else return 0;
}
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
int main()
{
    n=read();ask=read();
    t1=sqrt(n);t2=n/t1+1;
    for(ll i=1;i<=n;i++)
    {
        xx[i]=read();yy[i]=read();zz[i]=read();
        dx[i]=xx[i];
    }
    sort(dx+1,dx+n+1);
    ll tx=unique(dx+1,dx+n+1)-dx-1;//去重后的数组长度 
    for(ll i=1;i<=n;i++)
    {
        ll px=lower_bound(dx+1,dx+tx+1,xx[i])-dx;//大于等于xx[i]的第一个位置(这里实际上就是查找了xx[i]的位置,因为必然存在)
        ll Z=px/t1+1;       
        a[Z][++js[Z]].x=xx[i];
        a[Z][js[Z]].y=yy[i];
        a[Z][js[Z]].p=zz[i];
        //a[Z][]
    }
    for(ll i=1;i<=t2;i++)
    {
        sort(a[i]+1,a[i]+js[i]+1,cmp2);
        for(ll j=1;j<=js[i];j++)
        {
            sum[i][j]=sum[i][j-1]+a[i][j].p;//处理每块的基站功率的前缀和,sum[i][j]表示第i块前j个基站功率的和(方便统计答案) 
            b[i][j]=a[i][j].y; 
        }
    }
    for(ll i=1;i<=ask;i++)
    {
        a1=read();b1=read();a2=read();b2=read();
        ll px1=lower_bound(dx+1,dx+tx+1,a1)-dx;
        ll px2=upper_bound(dx+1,dx+tx+1,a2)-dx;/////二分查找左右边界点 
    //  cout<<px1<<" "<<px2<<endl<<endl; 
        ll ans=0;
        ll p=px1/t1+1,q=px2/t1+1;
        for(ll j=p+1;j<=q-1;j++)//中部查找 
        {
            ll py1=lower_bound(b[j]+1,b[j]+js[j]+1,b1)-b[j];
            ll py2=upper_bound(b[j]+1,b[j]+js[j]+1,b2)-b[j]-1;///上下界点查找 
            ans=ans+sum[j][py2]-sum[j][py1-1];
        }
        for(ll j=1;j<=js[p];j++)//js[p]:块p的大小
        //两端查找 
        {
            if(a[p][j].x>=a1&&a[p][j].y>=b1&&a[p][j].x<=a2&&a[p][j].y<=b2)
            {
                ans=ans+a[p][j].p;
            }
        }
        if(p!=q) //是否位于同一块内部,若不是,由于前面计算了块p的贡献,再计算一次块q的贡献(同一块无需再计算) 
        {
            for(ll j=1;j<=js[q];j++) 
            {
                if(a[q][j].x>=a1&&a[q][j].y>=b1&&a[q][j].x<=a2&&a[q][j].y<=b2)
                {
                    ans=ans+a[q][j].p;//在范围内,加上基站功率数 
                }
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}