LOJ #135. 二维树状数组 3:区间修改,区间查询 题解

博客园同步

原题链接

前置知识:

一维树状数组的区间修改与区间查询。

简要题意:

维护二维数组的矩阵加与矩阵查。

很显然,如果你用 二维线段树 的话,常数较大,加上要开 long long \text{long long} long long,很可能会 MLE + TLE \text{MLE + TLE} MLE + TLE 的双倍快乐。

所以我们要用 二维树状数组 解决这道题目。

考虑常规前缀和,二维的前缀和需要维护 4 4 4 个节点,我们也需要一一维护。

也就是我们要维护 4 4 4 个差分数组用来一一对应矩阵的四个角,并用 滚一维 的方式,把二维的更新。注意区间加和的细节。

时间复杂度: O ( n 2 log ⁡ 2 n ) − O ( log ⁡ 2 n ) \mathcal{O}(n^2 \log^2 n) - \mathcal{O}(\log^2 n) O(n2log2n)O(log2n).

实际得分: 100 p t s 100pts 100pts.

双倍经验:P4514 上帝造题的七分钟,不过这道题目需要大力卡常(本人卡了半天还是 81 81 81,自闭了, LOJ \text{LOJ} LOJ 评测器最快!)

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize(5000)
#pragma GCC optimize(1000000000)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include
using namespace std;

const int N=(1<<12)+1;
#define int long long

inline int read(){char ch=getchar(); int f=1; while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0; while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f;}

inline void write(int x) {
	if(x<0) {putchar('-');write(-x);return;}
	if(x<10) {putchar(char(x%10+'0'));return;}
	write(x/10);putchar(char(x%10+'0'));
}

int n,m;
struct BIT {
	int a[N][N];
	inline void add(int x,int y,int t) {
		for(register int i=x;i<=n;i+=(i&-i))
		for(register int j=y;j<=m;j+=(j&-j)) a[i][j]+=t;
	} //加

	inline int query(int x,int y) {
		int t=0;
		for(register int i=x;i>=1;i-=(i&-i))
		for(register int j=y;j>=1;j-=(j&-j)) t+=a[i][j];
		return t;
	} //询问
}A,B,C,D; //四个树状数组

inline int calc(int x,int y) {
	return A.query(x,y)*(x*y+x+y+1)-B.query(x,y)*(y+1)
		-C.query(x,y)*(x+1)+D.query(x,y);
} //计算二维前缀和的答案

inline void Add(int x,int y,int t) {
	A.add(x,y,t); B.add(x,y,t*x);
	C.add(x,y,t*y); D.add(x,y,t*x*y);
} //修改四个节点

signed main() {
	n=read(),m=read(); int op;
	while(cin>>op) {
		int x1=read(),y1=read(),x2,y2,t;
		if(op==1) {
			x2=read(),y2=read(); t=read(); Add(x1,y1,t); Add(x1,y2+1,-t);
			Add(x2+1,y1,-t); Add(x2+1,y2+1,t);
		} else x2=x1,y2=y1,write(calc(x2,y2)-calc(x1-1,y2)-calc(x2,y1-1)+calc(x1-1,y1-1)),
		putchar('\n');
	}
	return 0;
}

你可能感兴趣的:(树状数组)