我是靠谱客的博主 难过纸飞机,最近开发中收集的这篇文章主要介绍「BZOJ2683」 简单题 - CDQ分治,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

题目描述

你有一个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终止程序

输入格式

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

输出格式

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

数据范围

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

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

分析

若N比较小,则可以用二维的树状数组或线段树来做,但是500000,空间开不下,于是考虑离线CDQ。

所谓CDQ分治,是由金牌选手陈丹琪提出来的。普通的分治,只是递归左边,递归右边,返回。而CDQ分治分别递归左右两边,再计算由左边对右边的操作产生的影响。其实归并排序求逆序对就是简单的CDQ分治,先递归左边,再递归右边,再用两个指针分别从L,mid+1开始扫描,并计算左边与右边共同所产生的逆序对数。

对于本题,先将所有的操作离线读入,并将一个查询用前缀和的思想分为四个查询,修改就直接读入。递归边界是l==r,此时不需要处理,只有一个操作。递归处理左右两边,此时(L,mid)的时间一定再(mid+1,R)的前面,于是规定一个优先级,x为第一关键字,从小到大排,类型为第二关键字,修改优于查询。以后说操作的小于都按照此规定来。用两个指针p=L,q=mid+1,类似归并排序求逆序对一样。对于p,q,若p所指向的操作小于q所指向的操作,则说明p要优先,若p为修改,则再树状数组对应位置修改,p++,否则不管,因为左边的询问再递归左边的时候已经处理好了;否则,说明q要优先,若q为查询,则再树状数组对应位置查询,并记录答案。之后在将树状数组复位,以保证时间复杂度。对于当前所有的操作,用一个temp数组临时存下来,最后要赋值给原数组,保证x的有序。

这里的树状数组又是怎么回事?实际上,通过前缀和,已经将问题转化为x<=x0,y<=y0的所有格子上的数的和。因为x已经有序,即在当前操作之前的所有操作都是对与x<=x0,若此时y也有序,则直接统计即可。但可惜的是y并不有序,所以用树状数组统计y<=y0的所有数的和。最后一定要用循环复位,即将加上的减去,不能直接memset,要保证时间复杂度仅仅与L,R有关。

以上就是CDQ分治在本题的大致流程。不得不佩服陈丹琪,能想出这种分治算法。

接下来就是令人兴奋的代码环节。

代码

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long LL;
const int N=500005;
const int M=300005*4;
struct Query {
LL x,y,type,w;//x,y为坐标,type为类型:0为修改,1为查询
LL val,id;//val为修改时加的值,id为查询后原答案的位置 
//w为查询时是该点的答案对总答案的贡献,加/减 
bool operator<(const Query&a) const {
if (x!=a.x) return x<a.x;//x为第一关键字 
return type<a.type;//type为第二关键字 
}
}a[M],temp[M];
LL n,op,pt,num;
LL ans[M],nm[M];
struct Bit {//结构体的树状数组更清晰 
LL c[N];
#define lowbit(x) ((x)&-(x))
void Add(int x,LL v) {
while (x<=n+1) {
c[x]+=v;
x+=lowbit(x);
}
}
int Ask(int x) {
int ret=0;
while (x) {
ret+=c[x];
x-=lowbit(x);
}
return ret;
}
}bit;
void CDQ(int l,int r) {
if (l==r) return;//边界 
int mid=(l+r)>>1;
CDQ(l,mid);
CDQ(mid+1,r);
int p=l,q=mid+1,t=l;
while (p<=mid&&q<=r) {//与归并求逆序对类似 
if (a[p]<a[q]) {
if (a[p].type==1) {
bit.Add(a[p].y,a[p].val);
}
temp[t++]=a[p++];
} else {
if (a[q].type==2) ans[a[q].id]+=a[q].w*bit.Ask(a[q].y);
temp[t++]=a[q++];
}
}
while (q<=r) {//对于剩余的操作 
if (a[q].type==2) ans[a[q].id]+=a[q].w*bit.Ask(a[q].y);
temp[t++]=a[q++];
}
for (int i=l;i<p;i++)//树状数组复位 
if (a[i].type==1) bit.Add(a[i].y,-(a[i].val));
while (p<=mid) temp[t++]=a[p++];
for (int i=l;i<=r;i++)
a[i]=temp[i];
}
int main() {
scanf("%lld",&n);
while (scanf("%lld",&op),op!=3) {
LL x,y,xx,yy,v;
if (op==1) {
scanf("%lld%lld%lld",&x,&y,&v);
x++;y++;//防止树状数组下标为0 
a[++pt]=(Query){x,y,1,0,v};//插入查询 
} else {
scanf("%lld%lld%lld%lld",&x,&y,&xx,&yy);
++num;
x++;y++;xx++;yy++;
a[++pt]=(Query){x-1,y-1,2,1,0,num};//拆分询问 
a[++pt]=(Query){xx,yy,2,1,0,num};
a[++pt]=(Query){x-1,yy,2,-1,0,num};
a[++pt]=(Query){xx,y-1,2,-1,0,num};
}
}
CDQ(1,pt);
for (int i=1;i<=num;i++) printf("%lldn",ans[i]);
return 0;
}

最后

以上就是难过纸飞机为你收集整理的「BZOJ2683」 简单题 - CDQ分治的全部内容,希望文章能够帮你解决「BZOJ2683」 简单题 - CDQ分治所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部