BZOJ1176 || 洛谷P4390 [Balkan2007]Mokia【CDQ分治】

时空限制 2000ms / 128MB

题目描述

摩尔瓦多的移动电话公司摩基亚(Mokia)设计出了一种新的用户定位系统。和其他的定位系统一样,它能够迅速回答任何形如“用户C的位置在哪?”的问题,精确到毫米。但其真正高科技之处在于,它能够回答形如“给定区域内有多少名用户?”的问题。

在定位系统中,世界被认为是一个W×W的正方形区域,由1×1的方格组成。每个方格都有一个坐标(x,y),1<=x,y<=W。坐标的编号从1开始。对于一个4×4的正方形,就有1<=x<=4,1<=y<=4(如图)
这里写图片描述
请帮助Mokia公司编写一个程序来计算在某个矩形区域内有多少名用户。

输入格式:

有三种命令,意义如下:

0 W 初始化一个全零矩阵。本命令仅开始时出现一次。
1 x y A 向方格(x,y)中添加A个用户。A是正整数。
2 X1 Y1 X2 Y2 查询X1<=x<=X2,Y1<=y<=Y2所规定的矩形中的用户数量
3 无参数 结束程序。本命令仅结束时出现一次。

输出格式:

对所有命令2,输出一个一行整数,即当前询问矩形内的用户数量。

说明

对于所有数据:

1<=W<=2000000
1<=X1<=X2<=W
1<=Y1<=Y2<=W
1<=x,y<=W
0< A<=10000
命令1不超过160000个。
命令2不超过10000个。


题目分析

尝试将询问转化为二维前缀和
对于询问 ( x 1 , y 1 , x 2 , y 2 ) (x_1,y_1,x_2,y_2) (x1,y1,x2,y2)
变成 ( 1 , 1 , x 2 , y 2 ) − ( 1 , 1 , x 2 , y 1 − 1 ) − ( 1 , 1 , x 1 − 1 , y 2 ) + ( 1 , 1 , x 1 − 1 , y 1 − 1 ) (1,1,x_2,y_2)-(1,1,x_2,y_1-1)-(1,1,x_1-1,y_2)+(1,1,x_1-1,y_1-1) (1,1,x2,y2)(1,1,x2,y11)(1,1,x11,y2)+(1,1,x11,y11)

即对于一个 ( x i , y i ) (x_i,y_i) (xi,yi)
我们需要查询满足 x j < = x i , y j < = y i x_j<=x_i,y_j<=y_i xj<=xi,yj<=yi j j j的数量

为了保证当前询问不会查询到后面给出的信息
我们给每个元素添加时间戳

到这里就是裸的三维偏序
CDQ分治求解即可


#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;
#define lowbit(x) ((x)&(-x))
 
int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}
 
const int maxn=5000010;
int n;
// t时间戳, opt 1添加/2询问, k该询问是矩形的哪个顶点, id询问的编号, ans该询问的答案
struct node{int t,x,y,opt,cnt,k,id,ans;}a[maxn],b[maxn]; 
int sum[maxn];
int ans[maxn],tot,Q;

void add(int x,int v){ for(int i=x;i<=n;i+=lowbit(i))sum[i]+=v;}
int qsum(int x){ int res=0; for(int i=x;i>0;i-=lowbit(i))res+=sum[i]; return res;}
 
void CDQ(int ll,int rr)
{
    if(ll==rr) return;
    int mid=ll+rr>>1;
    CDQ(ll,mid); CDQ(mid+1,rr);
    
    int t1=ll,t2=mid+1,p=ll;
    while(t2<=rr)
    {
        while(a[t1].x<=a[t2].x&&t1<=mid)
        {
            if(a[t1].opt==1) add(a[t1].y,a[t1].cnt);
            b[p++]=a[t1++];
        }
        if(a[t2].opt==2) a[t2].ans+=qsum(a[t2].y);
        b[p++]=a[t2++];
    }
    
    for(int i=ll;i<t1;++i)
    if(a[i].opt==1) add(a[i].y,-a[i].cnt);
    
    while(t1<=mid) b[p++]=a[t1++];
    while(t2<=rr) b[p++]=a[t2++];
    for(int i=ll;i<=rr;++i) a[i]=b[i];
}
 
int main()
{
    while(1)
    {
        int opt=read();
        if(opt==3) break;
        if(opt==0) n=read();
        if(opt==1)
        {
            int x=read(),y=read(),cnt=read();
            a[++tot]=(node){tot,x,y,1,cnt,0,0,0};
        }
        else if(opt==2)
        {
            int aa=read(),b=read(),c=read(),d=read(); Q++;
            a[++tot]=(node){tot,c,d,2,0,1,Q,0};
            a[++tot]=(node){tot,aa-1,b-1,2,0,1,Q,0};
            a[++tot]=(node){tot,aa-1,d,2,0,-1,Q,0};
            a[++tot]=(node){tot,c,b-1,2,0,-1,Q,0};
        }
    }
    
    CDQ(1,tot);
    for(int i=1;i<=tot;++i)
    if(a[i].opt==2) ans[a[i].id]+=a[i].k*a[i].ans;
    
    for(int i=1;i<=Q;++i)
    printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值