CDQ分治
题目描述
你有一个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 | 无 | 终止程序 |
输入格式
输入文件第一行一个正整数N。
接下来每行一个操作。
输出格式
对于每个2操作,输出一个对应的答案。
输入样例
4
1 2 3 3
2 1 1 3 3
1 2 2 2
2 2 2 3 4
3
输出样例
3
5
提示
1<=N<=500000,操作数不超过200000个,内存限制20M。
对于100%的数据,操作1中的A不超过2000。
分析
这是KD_tree板子题!
快强制在线写KD_tree!
(不是)
首先我们把所有询问拆成四个,就你二维前缀和怎么拆的你就怎么拆。
就是把对于[x1,y1,x2,y2] (其中x1<=x2,y1<=y2)的询问拆成①[1,1,x1,y1],②[1,1,x1,y2],③[1,1,x2,y1],④[1,1,x2,y2]这四个询问,那么答案就等于④-②-③+①。
这样的话我们就可以统一两种操作的格式了。
对于每个操作,
o
p
t
opt
opt代表它的类型,为1则为修改,为2则为查询;
t
i
m
tim
tim代表它是第几个被执行的操作,即被执行的时间;
l
e
f
lef
lef、
r
i
g
rig
rig代表所操作的点的横纵坐标(之所以不用
x
,
y
x,y
x,y是因为我想保持3个字母的格式);
v
a
l
val
val,当
o
p
t
=
1
opt=1
opt=1时,代表要加上几,当
o
p
t
=
2
opt=2
opt=2时,代表这个询问对答案是加还是减;$$
随后我们把所有修改和询问存起来,按照x坐标排序。
这个[1,1,x1,y1]就按x1排序吧QwQ,难道要按1排序?
为什么要排序x?
我们知道,一个modify要对一个query起作用,当且仅当①modify发生在query之前,②modify的点的x坐标<=query的点的x坐标,③modify的点的y坐标<=query的点的y坐标。
这是三维偏序!
所以时间分治,x排序,y树状数组。
当然可以排序时间,可以另外两个怎么维护?
排序y的话,还是时间分治,x树状数组。
其实你也可以第一维排序,第二维CDQ,第三维CDQ分治的。
三维偏序是什么?你这个“所以”来得实在是太陡然了吧!
?
原谅我写的是一篇题解而非讲解,我会补一份讲解的。
讲解什么时候补?
在做了在做了在做了。在学拼音了在学拼音了在学拼音了。
因为我们已经按x排序过了,所以对于任意两个操作,只需要前者下标小于后者,就说明前者的x坐标小于后者的。
我们设
s
o
l
v
e
(
l
,
r
)
solve(l,r)
solve(l,r)代表实行
[
l
,
r
]
[l,r]
[l,r]中的所有操作,如果是修改就修改如果是查询就记录一下这个查询的答案。
读标题发现这篇博客写的是CDQ分治,哦,要分治。
于是我们就调用
s
o
l
v
e
(
l
,
m
i
d
)
,
s
o
l
v
e
(
m
i
d
+
1
,
r
)
solve(l,mid),solve(mid+1,r)
solve(l,mid),solve(mid+1,r)!——大功告成,写完了!
(先不管你的函数里面除了调用以外没有别的操作吧)按照你的定义,
s
o
l
v
e
(
m
i
d
+
1
,
r
)
solve(mid+1,r)
solve(mid+1,r)是实行且仅实行
[
m
i
d
+
1
]
,
r
[mid+1],r
[mid+1],r的所有操作,包括修改和查询,可是,你在
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r]查询的时候,你怎么知道在
[
l
,
m
i
d
]
[l,mid]
[l,mid]里的修改不会影响到它?也就是说,你的
s
o
l
v
e
(
l
,
r
)
solve(l,r)
solve(l,r)并不等价于
s
o
l
v
e
(
l
,
m
i
d
)
,
s
o
l
v
e
(
m
i
d
+
1
,
r
)
solve(l,mid),solve(mid+1,r)
solve(l,mid),solve(mid+1,r)。
那没问题,只要我们单独计算一下用上左边的修改能对右边的查询造成怎样的影响就好了。
咋单独计算啊?
for循环+树状数组计算啊?你就从
l
e
f
lef
lef到
r
i
g
rig
rig枚举过去,在左边遇到一个修改就修改,在右边遇到一个查询就查询,这不是单点修改单点查询吗?
。。。
还有你树状数组完以后记得清零,不要用
m
e
m
s
e
t
memset
memset,就再for循环一次,遇到加就再往树状数组里加它的相反数就好了。
……我想请问一下您,您刚才不是说什么要满足三个条件,修改才能对询问起作用,你为什么把所有“左边”的修改直接塞进树状数组,把所有“右边”的查询都求出来?我记得您仅仅只保证了x坐标的这一个限制条件是满足的啊。
这就是你不懂啊,只要你会用树状数组求逆序对,不,这就是一个求前缀和的简单问题,你就应该知道,如果我修改的y坐标比你查询的y坐标大,你用树状数组来求前缀和根本找不到我。
我明白了,那么时间呢?
你就是你心太急了,你看看我的代码,我才讲到我的cdq函数的一半呢。
……您继续说。
我时间不够了所以就水一点赶快跟你讲完,你看看这个代码,它是不是有点眼熟?是不是有点像归并排序?
我看看……哦,原来这个lef,rig,mid代表的不是下标而是时间啊,所以你这是在把它按照时间来分成两块,然后归并排序,那你为什么……你人呢?
(此时因为昨天的经验教训所以趁着还没到23:00就狂奔走了)
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
inline void Read(int &p)
{
p=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
}
const int MAXN=502030;
struct ark
{
int opt,tim,lef,rig,id,val;
ark(){}
ark(int o,int t,int l,int r,int i,int v){opt=o,tim=t,lef=l,rig=r,id=i,val=v;}
bool operator < (const ark &q) const
{
return (lef==q.lef)?opt<q.opt:lef<q.lef;
}
}p[MAXN*2],q[MAXN*2];
int n,m,a,b,c,d,opt,tot,cnt,ans[MAXN],bit[MAXN];
#define lowbit(i) (i)&(-(i))
inline void update(int pos,int del) {while(pos<=n) bit[pos]+=del,pos+=lowbit(pos);}
inline int getsum(int pos) {int sum=0; while(pos) sum+=bit[pos],pos-=lowbit(pos); return sum;}
void cdq(int lef,int rig)
{
if(lef==rig) return ;
int mid=(lef+rig)>>1;
for(int i=lef;i<=rig;i++)
{
if(p[i].opt==1 && p[i].tim<=mid) update(p[i].rig,p[i].val);
if(p[i].opt==2 && p[i].tim>mid) ans[p[i].id]+=p[i].val*getsum(p[i].rig);
}
for(int i=lef;i<=rig;i++)
if(p[i].opt==1 && p[i].tim<=mid) update(p[i].rig,-p[i].val);
int l=lef,r=mid+1;
for(int i=lef;i<=rig;i++)
if(p[i].tim<=mid) q[l++]=p[i];
else q[r++]=p[i];
for(int i=lef;i<=rig;i++) p[i]=q[i];
cdq(lef,mid),cdq(mid+1,rig);
}
int main()
{
Read(n);
while(1)
{
Read(opt);
if(opt==1) Read(a),Read(b),Read(c),p[++cnt]=ark(1,cnt,a,b,0,c);
else if(opt==2)
{
Read(a),Read(b),Read(c),Read(d),a--,b--,
p[++cnt]=ark(2,cnt,a,b,++tot,1),p[++cnt]=ark(2,cnt,c,d,tot,1),
p[++cnt]=ark(2,cnt,a,d,tot,-1),p[++cnt]=ark(2,cnt,c,b,tot,-1);
}
else break;
}
sort(p+1,p+cnt+1),cdq(1,cnt);
for(int i=1;i<=tot;i++) printf("%d\n",ans[i]);
}
说在后面
昨天晚自习考了试?考了试?考到11:00,寝室那边被老师jue?
所以就咕了“最迟10月5号你会看到CDQ分治的博客”。
我对不起我自己。
不知道大家看出来这是两小儿辩CDQ了吗,不说了,寝室又要关门了,明天有空的话把归并那部分在说的详细一点吧。
虽然很丑,但还是迈出了第一步,rererestarting。
2019-10-6 22:55:38,我赶时间,再见,晚安。