CDQ分治 【bzoj2683 && bzoj1176】

本文介绍了一种基于时间分治的CDQ分治算法,用于解决二维棋盘上的更新和查询问题。通过将询问拆分并排序,利用归并排序的思想,在递归过程中处理答案。对比了CDQ分治与二维树状数组两种方法的空间和时间复杂度。

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

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

题目分析:(CDQ分治)
借鉴自YihAN_Z的博客

CDQ分治的主要思想是按照时间进行分治。
首先把一个询问拆成4个,把所有的点按照x坐标进行排序,然后在处理的区间内把时间小于等于mid的扔到左边,时间大于mid的扔到右边,这是x坐标在左右区间还是有序的,然后我们处理左区间的修改对右区间询问的影响。
并且递归处理左区间和右区间。

个人感觉整个过程就像是有一个按照时间排好序的序列,然后你把这个序列按照x坐标打乱,然后在CDQ的过程中就像是再按照时间关键字重新做一遍归并排序(当然CDQ分治最后还要把这个序列还原,相当于没排……),然后在这个过程中处理出答案。

此题用CDQ分治的好处就是降了一维,省空间,时间上如果n和m差距不是很大,应该是差不多的。
二维树状数组:空间O(n^2) 时间 O (m * logn * logn)
CDQ分治: 空间O(n) 时间 O(m * logm * logn)
可以看出CDQ分治的优越性。

以下是bzoj2683的代码,bzoj1176同此题,所有处理都是一样的,只不过在最后计算答案的时候加上初始的s乘以询问矩阵的大小再输出就可以了(那个初始的s就是来打个酱油,真的没什么大用)。

代码如下(↓今天代码不知为何奇丑无比):

#include<cstdio>
#include<algorithm>
#define N 520000
#define M 220000
using namespace std;
struct operation{
    int x,y,mode,tim,ans;
    operation(){}
    operation(int x,int y,int mode,int tim,int ans):x(x),y(y),mode(mode),tim(tim),ans(ans){}
    bool operator < (const operation &c) const { return x<c.x; }
}q[M*4],tmp[M*4];

int n,opt,x,y,x1,y1,x2,y2,z,top,timestamp;
int c[N],d[N];

bool cmp (operation a,operation b) {return a.tim<b.tim;}
int lowbit(int x) {return x&-x;}
void change(int x,int v)
{
    for(;x<=n;x+=lowbit(x))
    {
        if(d[x]!=timestamp) c[x]=0,d[x]=timestamp;
        c[x]+=v;
    }
    return;
}
int query(int x)
{
    int ans=0;
    for(;x;x-=lowbit(x)) if(d[x]==timestamp) ans+=c[x];
    return ans;
}
void CDQ(int l,int r)
{
    if(l==r) return;
    int mid=l+r>>1;
    int l1=l,l2=mid+1;
    for(int i=l;i<=r;i++)
        if(q[i].tim<=mid) tmp[l1++]=q[i];
        else              tmp[l2++]=q[i];
    for(int i=l;i<=r;i++) q[i]=tmp[i];
    CDQ(mid+1,r); CDQ(l,mid);
    l1=l; l2=mid+1; timestamp++;
    for(;l2<=r;l2++)
    {
        for(;q[l1].x<=q[l2].x && l1<=mid;l1++)
            if(q[l1].mode==1) change(q[l1].y,q[l1].ans);
        if(q[l2].mode==2) q[l2].ans+=query(q[l2].y);
    }
    l1=l; l2=mid+1;
    for(int i=l;i<=r;i++)
        if(q[l1]<q[l2] && l1<=mid || l2>r) tmp[i]=q[l1++];
        else tmp[i]=q[l2++];
    for(int i=l;i<=r;i++) q[i]=tmp[i];
    return;
}

int main()
{
    scanf("%d",&n);
    while(scanf("%d",&opt)!=EOF)
    {
        if(opt==3) break;
        if(opt==1)
        {
            scanf("%d%d%d",&x,&y,&z);
            q[++top]=operation(x,y,1,top,z);
        }
        else
        {
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
            q[++top]=operation(x1-1,y1-1,2,top,0);
            q[++top]=operation(x1-1,  y2,2,top,0);
            q[++top]=operation(  x2,y1-1,2,top,0);
            q[++top]=operation(  x2,  y2,2,top,0);
        }

    }
    sort(q+1,q+1+top);
    CDQ(1,top);
    sort(q+1,q+1+top,cmp);
    for(int i=1;i<=top;i++)
        if(q[i].mode==2)
        {
            int ans=0;
            ans+=q[i++].ans;
            ans-=q[i++].ans;
            ans-=q[i++].ans;
            ans+=q[i  ].ans;
            printf("%d\n",ans);
        }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值