bzoj2683 简单题

234 篇文章 0 订阅
148 篇文章 0 订阅

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

终止程序
HINT

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

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

首先考虑的是二维数据结构,树状数组或线段树。但是空间复杂度O(n^2),无法承受。
采用CDQ分治。
把所有询问拆分成四个,然后按x为主要关键字、操作顺序为次要关键字排序。
对于一段正在处理的区间(l..r),需要处在这个下标范围内的操作序号恰好也为l..r(这一点如何保证在之后说)。
这样在x有序的情况下,计算所有操作序号为 【注意,是操作序号,不是目前所在的数组下标。】 l..mid的修改对mid+1..r的询问的影响在时间和空间上都可以线性完成。从左往右扫描,按y维护树状数组即可。
所以数组虽然是按x排序的,但并不影响按照操作序号来计算影响。
处理完了之后,还要进行分治。
之前说过要保证处理的区间(l..r)内的操作序号也为l..r,【在这里具体就是l..mid存放l..mid,mid+1..r存放mid+1..r】并且还需要保证是按x排序,所以从开头和中间维护两个指针,线性扫描并且分类存放,最后拷贝回来即可。
注意不要用memset,否则会超时。
最后说一点关于CDQ分治的理解。这道题总共有三个维度:时间,x,y。
通常的做法是,按时间线性扫描,然后维护x,y,这样就需要二维数据结构来维护。
而CDQ分治则是按x线性扫描,数据结构里只维护y,这样只需要一维数据结构。那时间怎么办呢?注意到,时间对结果的影响就是,发生在后的修改不会对发生在前的询问产生影响。CDQ分治就是通过按照时间分治来解决,使每一次具体计算时,所有需要考虑的修改都在所有需要考虑的询问之前,也就是所有需要考虑的修改都会对所有需要考虑的询问产生影响,这样就不用考虑时间了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct com
{
    int k,x,y,z,num,p;
    bool operator < (const com &c) const
    {
        if (x<c.x) return 1;
        if (x>c.x) return 0;
        return p<c.p;
    }
}a[800010],aa[800010];
int s[500010],ans[200010],m,n,cnt;
void solve(int l,int r)
{
    if (l==r) return;
    int i,j,k,p,q,x,y,z,w,mid=(l+r)/2,p1,p2;
    for (i=l;i<=r;i++)
    {
        if (a[i].p<=mid&&a[i].k==0)
        {
            for (j=a[i].y;j<=n;j+=j&-j)
              s[j]+=a[i].z;
        }
        if (a[i].p>mid&&a[i].k)
        {
            for (j=a[i].y;j;j-=j&-j)
              ans[a[i].num]+=s[j]*a[i].k;
        }
    }
    for (i=l;i<=r;i++)
      if (a[i].p<=mid&&a[i].k==0)
        for (j=a[i].y;j<=n;j+=j&-j)
          s[j]-=a[i].z;
    p1=l;
    p2=mid+1;
    for (i=l;i<=r;i++)
      if (a[i].p<=mid)
        aa[p1++]=a[i];
      else
        aa[p2++]=a[i];
    for (i=l;i<=r;i++)
      a[i]=aa[i];
    solve(l,mid);
    solve(mid+1,r);
}
int main()
{
    int i,j,k,x,y,z,w;
    scanf("%d",&n);
    while (scanf("%d",&k)&&k!=3)
    {
        if (k==1)
        {
            m++;
            scanf("%d%d%d",&a[m].x,&a[m].y,&a[m].z);
            a[m].k=0;
            a[m].p=m;
        }
        else
        {
            scanf("%d%d%d%d",&x,&y,&z,&w);
            cnt++;
            m++;
            a[m]=(com){1,x-1,y-1,0,cnt,m};
            m++;
            a[m]=(com){-1,z,y-1,0,cnt,m};
            m++;
            a[m]=(com){-1,x-1,w,0,cnt,m};
            m++;
            a[m]=(com){1,z,w,0,cnt,m};
        }
    }
    sort(a+1,a+m+1);
    solve(1,m);
    for (i=1;i<=cnt;i++)
      printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值