bzoj2683 简单题 (cdq分治 + 树状数组)

bzoj2683 简单题

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=2683

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

命令参数限制内容
1 x y A1<=x,y<=N,A是正整数将格子x,y里的数字加上A
2 x1 y1 x2 y21<=x1<= x2<=N,1<=y1<= y2<=N输出x1 y1 x2 y2这个矩形内的数字和
3终止程序

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

题解:
内存20M,又因为N很大,上数据结构比较困难。
于是想到cdq分治。
对于一个查询(x1,y1,x2,y2)
我们把它分成四个查询 +(x1-1,y1-1),-(x1-1,y2), -(x2,y1-1) ,+(x2,y2),前缀和的加加减减。
这样只需考虑哪些操作会对我的查询有影响。
三个参数(x,y,time)
显然是三维都<=当前查询就会有影响,
这样就成了一个三维偏序问题,只不过树状数组里面存的,是带来的影响(加的数值)
于是cdq分治时用作半边的所有操作,去计算对右边所有询问的影响即可。

树状数组用了的才清。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=500005;
const int M=1000005;
struct node
{
    int typ,x,y,w,id; //类型,xy坐标,时刻, 影响(权值 或 +/-),ID
    friend bool operator<(const node &A,const node &B)
    {return (A.x!=B.x)? A.x<B.x:A.y<=B.y;} 
}Q[M],R[M];
int n,tim=0,tail=0,qs=0,ans[N]; 
struct B_I_T
{
    int c[N]; 
    void add(int x,int val)
    {
        for(int i=x;i<=n;i=i+(i&(-i))) c[i]+=val;
    }
    int query(int x)
    {
        int ret=0;
        for(int i=x;i>0;i=i-(i&(-i))) ret+=c[i];
        return ret;
    }
    void clear(int x)
    {
        for(int i=x;i<=n;i=i+(i&(-i))) c[i]=0;
    }
}Bit;
void cdq(int lf,int rg)
{
    if(rg-lf<=0) return;
    int mid=(lf+rg)>>1; 
    cdq(lf,mid); cdq(mid+1,rg);
    int p1=lf; int p2=mid+1; int tail=lf-1;
    while(p1<=mid&&p2<=rg)
    {
        if(Q[p1]<Q[p2]) 
        {
            if(Q[p1].typ==0) Bit.add(Q[p1].y,Q[p1].w);
            R[++tail]=Q[p1++]; 
        }
        else 
        {
            if(Q[p2].typ==1) ans[Q[p2].id]+=Q[p2].w*Bit.query(Q[p2].y);
            R[++tail]=Q[p2++];
        }

    }
    while(p1<=mid) R[++tail]=Q[p1++];
    while(p2<=rg)
    {
        if(Q[p2].typ==1) ans[Q[p2].id]+=Q[p2].w*Bit.query(Q[p2].y); 
        R[++tail]=Q[p2++];  
    }
    for(int i=lf;i<=rg;i++)
    {
        Bit.clear(R[i].y);  
        Q[i]=R[i];
    }
}
int main()
{
    scanf("%d",&n); n++;
    while(1)
    {
        int opt;
        scanf("%d",&opt);  
        if(opt==1)//1 x y A ,将格子x,y里的数字加上A
        {   
            ++tail; scanf("%d%d%d",&Q[tail].x,&Q[tail].y,&Q[tail].w);  Q[tail].x++; Q[tail].y++;
            Q[tail].typ=0; 
        }
        else if(opt==2)//2 x1 y1 x2 y2,输出x1 y1 x2 y2这个矩形内的数字和
        {
            int x1,y1,x2,y2;
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2); x1++; x2++; y1++; y2++;
            qs++;
            Q[++tail].typ=1; Q[tail].x=x1-1; Q[tail].y=y1-1; Q[tail].w=1;  Q[tail].id=qs;
            Q[++tail].typ=1; Q[tail].x=x1-1; Q[tail].y=y2; Q[tail].w=-1; Q[tail].id=qs;
            Q[++tail].typ=1; Q[tail].x=x2; Q[tail].y=y1-1; Q[tail].w=-1; Q[tail].id=qs; 
            Q[++tail].typ=1; Q[tail].x=x2; Q[tail].y=y2; Q[tail].w=1; Q[tail].id=qs;
        }
        if(opt==3) break;
    }
    cdq(1,tail);
    for(int i=1;i<=qs;i++) printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值