bzoj2683 简单题

Description

你有一个N*N的棋盘,每个格子内有一个整数,初始时的时候全部为0,现在需要维护两种操作:

 

命令

参数限制

内容

1 x y A

1<=x,y<=N,A是正整数

将格子x,y里的数字加上A

2 x1 y1 x2 y2

1<=x1<= x2<=N

1<=y1<= y2<=N

输出x1 y1 x2 y2这个矩形内的数字和

3

终止程序

Input

输入文件第一行一个正整数N。
接下来每行一个操作。
 

Output

对于每个2操作,输出一个对应的答案。
 

Sample Input

4
1 2 3 3
2 1 1 3 3
1 2 2 2
2 2 2 3 4
3

Sample Output

3
5

HINT

1<=N<=500000,操作数不超过200000个,内存限制20M。


对于100%的数据,操作1中的A不超过2000。


分析:
CDQ分治入门题
典型的三维偏序模型:

  • 时间
  • x坐标
  • y坐标

网上很多题解都是把询问拆成了4个(二维前缀和)
实际上我们可以把询问拆成两个:
这里写图片描述

因为我们是按照时间顺序读入的
所以一开始时间这一维就已经有序了(已经消除了影响)

在CDQ分治的时候,我们先把左右区间按照x坐标排序(如果x坐标相同,修改就排到询问之前
在左右区间各设置一个指针:t1,t2
从前往后扫,相当于按照x进行一次归并
但是我们并不是真的归并起来
为什么呢?因为我们之后要把树状数组清零,我们不能打乱左右区间的所属元素
这样方便我们之后从左区间找到修改操作并且消除影响(这样也能节省时间)

在处理的时候我们还是遵循这个原则:左区间修改,右区间查询

tip

在排序的时候注意数组下标:

sort(po+L,po+M+1,cmp);      //[L,M]
sort(po+M+1,po+R+1,cmp);    //[M+1,R]

处理完询问后,不要忘了消除左区间修改的影响
这时候我们多设置了一个last变量

记录最后的一个修改操作进行到哪里

这样在消除影响的时候就可以节省时间了
(如果没有的话会T,毕竟sort也是挺费时的)

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N=500010;
struct node{
    int type,x,ya,yb,id,z;
};
node po[N<<1],q[N<<1];
int t[N],n,m,tot=0,totx=0,ans[N];

void add(int x,int z) {for (int i=x;i<=n;i+=(i&(-i))) t[i]+=z;}
int ask(int x) {int ans=0;for (int i=x;i>0;i-=(i&(-i))) ans+=t[i];return ans;}

int cmp(const node &a,const node &b)
{
    if (a.x!=b.x) return a.x<b.x;
    else return a.type<b.type;
}

void CDQ(int L,int R)
{
    if (L==R) return;
    int M=(L+R)>>1;
    CDQ(L,M); CDQ(M+1,R);

    sort(po+L,po+M+1,cmp);      //并不打乱两部分 
    sort(po+M+1,po+R+1,cmp);
    int t1=L,t2=M+1,last=0;
    while (t2<=R)    
    //注意循环结束的标示:因为我们只要处理完右区间的询问就行了,那些还在后面的修改没有任何意义(不会影响本次的询问处理) 
    {
        while (t1<=M&&po[t1].type!=1) t1++;
        while (t2<=R&&po[t2].type==1) t2++;

        if (t1<=M&&po[t1].x<=po[t2].x) {
            add(po[t1].ya,po[t1].z);
            last=t1++;
        }
        else if (t2<=R) {
            if (po[t2].type==2) ans[po[t2].id]-=ask(po[t2].yb)-ask(po[t2].ya-1),t2++;
            else ans[po[t2].id]+=ask(po[t2].yb)-ask(po[t2].ya-1),t2++;
        }
    }
    for (int i=L;i<=last;i++)
        if (po[i].type==1) add(po[i].ya,-po[i].z);
}

int main()
{
    scanf("%d",&n);
    int xa,ya,xb,yb,opt,z;
    for (;;)
    {
        scanf("%d",&opt);
        if (opt==1)
        {
            scanf("%d%d%d",&xa,&ya,&z);
            tot++;
            po[tot].type=1; po[tot].x=xa; po[tot].ya=ya; po[tot].z=z;
        }
        else if (opt==2)
        {
            scanf("%d%d%d%d",&xa,&ya,&xb,&yb);
            tot++; totx++;
            po[tot].type=2; po[tot].x=min(xa,xb)-1; 
            po[tot].ya=min(ya,yb); po[tot].yb=max(ya,yb); po[tot].id=totx;
            tot++;
            po[tot].type=3; po[tot].x=max(xa,xb); 
            po[tot].ya=min(ya,yb); po[tot].yb=max(ya,yb); po[tot].id=totx;
        }
        else break;
    }


    CDQ(1,tot);

    for (int i=1;i<=totx;i++) printf("%d\n",ans[i]);
    return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值