【BZOJ1176】【BOI2007】Mokia & 【BZOJ2683】简单题(CDQ分治+树状数组)

本文介绍了一个经典CDQ分治问题的解决思路,通过对矩阵查询问题的分析,利用前缀和与容斥原理,将二维查询转换为一维问题,采用树状数组实现高效查询与更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

维护一个W*W的矩阵,初始值均为S.每次操作可以增加某格子的权值,或询问某子矩阵的总权值.修改操作数M<=160000,询问数Q<=10000,W<=2000000.

Input

第一行两个整数,S,W;其中S为矩阵初始值;W为矩阵大小

接下来每行为一下三种输入之一(不包含引号):

“1 x y a”

“2 x1 y1 x2 y2”

“3”

输入1:你需要把(x,y)(第x行第y列)的格子权值增加a

输入2:你需要求出以左上角为(x1,y1),右下角为(x2,y2)的矩阵内所有格子的权值和,并输出

输入3:表示输入结束

Output

对于每个输入2,输出一行,即输入2的答案

Sample Input

0 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

保证答案不会超过int范围

题解:
经典的CDQ分治问题,属于较早期的题目。我们利用前缀和记录每个点到左上角的元素和,将询问通过容斥原理拆成4个,这时每个询问或修改都只涉及两维坐标,加上他们本身的时间顺序,构成了标准的三维偏序问题。
将询问与修改按X轴进行排序,对Y轴建立树状数组,那么如果X从小到大排,就相当于求Y轴上的前缀和。分治左右,然后计算修改在mid左边,查询在mid右边的情况。
以上也是大多数CDQ分治的套路。
代码如下:

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#define ll long long
#define inf 0x7f7f7f7f
#define lb(x) (x&(-x))
using namespace std;
ll read()
{
    ll x=0,f=1;
    char c=getchar();
    while(c<'0' || c>'9') {if(c=='-') f=-1;c=getchar();}
    while(c<='9' && c>='0') {x=x*10+c-'0';c=getchar();}
    return x*f;
}
int s,w,m,ans[10005],t[2000005],Count;
struct que
{
    int x,y,val,pos,id,opt;
    bool operator < (const que& b) const
    {
        if(x==b.x && y==b.y) return opt<b.opt;
        if(x==b.x) return y<b.y;
        return x<b.x;
    }
}q[200005],tmp[200005];
void addquery()
{
    int x1=read(),y1=read(),x2=read(),y2=read();
    int pos=++Count;
    q[++m].pos=pos;q[m].id=m;q[m].x=x1-1;q[m].y=y1-1;q[m].val=1;q[m].opt=2;
    q[++m].pos=pos;q[m].id=m;q[m].x=x2;q[m].y=y2;q[m].val=1;q[m].opt=2;
    q[++m].pos=pos;q[m].id=m;q[m].x=x1-1;q[m].y=y2;q[m].val=-1;q[m].opt=2;
    q[++m].pos=pos;q[m].id=m;q[m].x=x2;q[m].y=y1-1;q[m].val=-1;q[m].opt=2;
}
void add(int x,int val)
{
    while(x<=w)
    {
        t[x]+=val;
        x+=lb(x);
    }
}
int query(int x)
{
    int ret=0;
    while(x)
    {
        ret+=t[x];
        x-=lb(x);
    }
    return ret;
}
void cdq(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1,l1=l,l2=mid+1;
    for(int i=l;i<=r;i++)
    {
        if(q[i].id<=mid && q[i].opt==1) add(q[i].y,q[i].val);
        if(q[i].id>mid && q[i].opt==2) ans[q[i].pos]+=q[i].val*query(q[i].y);
    }
    for(int i=l;i<=r;i++) if(q[i].id<=mid && q[i].opt==1) add(q[i].y,-q[i].val);
    for(int i=l;i<=r;i++) 
    {
        if(q[i].id<=mid) tmp[l1++]=q[i];
        else tmp[l2++]=q[i];
    }
    for(int i=l;i<=r;i++) q[i]=tmp[i];
    cdq(l,mid);cdq(mid+1,r);
}
int main()
{
    s=read();w=read();
    while(1)
    {
        int opt=read();
        if(opt==1)
        {
            q[++m].x=read(),q[m].y=read(),q[m].val=read(),q[m].opt=1;
            q[m].id=m;
        }
        else if(opt==2) addquery();
        else break;
    }
    sort(q+1,q+m+1);
    cdq(1,m);
    for(int i=1;i<=Count;i++) printf("%d\n",ans[i]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值