【Balkan2007】Mokia
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:表示输入结束
"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范围
Solution
这道题的数据范围十分巨大,以至于各种各样的暴力数据结构都挂了。。。
我们需要学习一种新的方法来解决这一类问题,那就是神奇的CDQ分治。
这是一种离线的询问方法,它将修改操作和询问操作一视同仁,将它们按照操作时间进行排序。接着就是分治的步骤了。
我们将原有的询问分为左右两个集合,分别处理并记下答案。但是在这一操作之前,我们还需要让左集合的事件对右集合的询问产生影响,然后总和起来就得到了每一个询问的答案。
在这一道题中,我们知道可以用前缀和来得出一个子矩阵的总和,所以我们用CDQ分治x轴,树状数组维护y轴。
CODE
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
namespace Istream {
const int L=1<<15;
char buffer[L],*Z,*T;
inline char getchar() {
if(Z==T) {
T=(Z=buffer)+fread(buffer,1,L,stdin);
if(Z==T)return EOF;
}
return *Z++;
}
inline int read() {
char c;
int rec=0,f=1;
for(c=getchar(); c<'0'||c>'9'; c=getchar())if(c=='-')f=-1;
while(c>='0'&&c<='9')rec=(rec<<1)+(rec<<3)+(c-'0'),c=getchar();
return rec*f;
}
}
using namespace Istream;//一次性将缓冲区的全部文件读入,然后处理。只能用于文件输入输出。
const int N=500010;
const int M=2000100;
struct node {
int op,x,y,z,t,id;
} p[N],s[N];
int S,n,num,tot,id[N];
int h[M],ans[N];
inline bool cmp(const node &a,const node &b){return a.x<b.x;}//排序
void addquery(int i,int op,int x,int y,int z,int id) {
p[i].id=i;p[i].op=op;p[i].x=x;p[i].y=y;p[i].z=z;p[i].t=id;return ;
}//添加事件
void add(int pos,int x) {while (pos<=n) h[pos]+=x,pos+=(pos&(-pos));return ;}
long long sum(int pos) {long long t=0;while (pos>0) t+=h[pos],pos-=(pos&(-pos));return t;}
//简单的树状数组
void CDQ(int l,int r) {
if(l==r)return;
int mid=(l+r)>>1,i,t1=l-1,t2=mid,t=l;
for(i=l; i<=r; i++)
if(p[i].id<=mid)s[++t1]=p[i];
else s[++t2]=p[i];//在时间有序的情况下再按照已有的x排序进行排序
memcpy(p+l,s+l,sizeof(node)*(r-l+1));
for(i=mid+1; i<=r; i++)
if(p[i].op==2) {//将该询问之前的所有修改加入树状数组。
for(; t<=mid&&p[t].x<=p[i].x; t++)if(p[t].op==1)add(p[t].y,p[t].z);
ans[p[i].t]+=sum(p[i].y)*p[i].z;//确定加减
}
for(i=l; i<t; i++)if(p[i].op==1)add(p[i].y,-p[i].z);//退回发生的影响,方便接下来的分治。
CDQ(l,mid);CDQ(mid+1,r);//分治
return ;
}
int ttt[500010];//负责计算出每一个询问的基础值
int main() {
int i,k,x,y,z,w;
S=read();n=read();
while (1) {
k=read();
if (k==3) break;
if (k==1) {
x=read();y=read();z=read();
addquery(++tot,1,x,y,z,0);
}
else {
x=read();y=read();z=read();w=read();num++;
ttt[num]=(z-x+1)*(w-y+1)*S;//子矩阵大小乘以矩阵初值
addquery(++tot,2,z,w,1,num);addquery(++tot,2,x-1,y-1,1,num);
addquery(++tot,2,x-1,w,-1,num);addquery(++tot,2,z,y-1,-1,num);
//将一个区间询问拆分成为四个前缀和的加减操作
}
}sort(p+1,p+1+tot,cmp);
solve(1,tot);
for (i=1; i<=num; i++)cout<<ans[i]+ttt[i]<<'\n';//输出答案
return 0;
}