二维树状数组

前置知识

树状数组(不会就学一下再来)

简介

因为树状数组可以非常简洁解决序列上的一些问题,所以考虑能否用树状数组解决矩阵(二维序列)的问题。
比较暴力的想法是对于每一横行建一个树状数组,再对每一列建一个树状数组统计答案。
但这样显然要\(n+m\)个树状数组,但是我们发现这些树状数组复用了一些节点,所以我们考虑不用真的建出横行的树状数组,直接用竖行的就可以储存所有信息。

用法

单点修改 区间查询

树状数组直接记和,查询就是二维区间和的式子,单点修改就是直接改,考虑会对哪些树状数组的点进行修改即可。

void add(int x,int y,int d)
{
    for(int i=x;i<=n;i+=lowbit(i))
        for(int j=y;j<=m;j+=lowbit(j))
            sh[i][j]+=d;
}
LL query(int x,int y)
{
    LL ret=0;
    for(int i=x;i;i-=lowbit(i))
        for(int j=y;j;j-=lowbit(j))
            ret+=sh[i][j];
    return ret;
}

区间修改 单点查询

区间修改就不能直接记和,记差分(二维差分为\(d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]\),你会发现性质与一维差分相同)。
考虑一次修改会对差分数组带来什么影响。(对黄色区间区间加\(w\),差分数组四个红色点要修改)
image

所以\(a[i][j]=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]\)
树状数组代码与上面相同,主函数改一下修改即可

区间修改 区间查询

结合上两个即可。
区间修改用差分,但区间求和怎么办?

\[sum[i][j]=\sum_{i=1}^{x}\sum_{j=1}^{y}\sum_{h=1}^{i}\sum_{k=1}^{j}d[h][k] \]

\[sum[i][j]=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j](x-i+1)(y-j+1) \]

\[sum[i][j]=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]ij+d[i][j](xy+x+y+1)-d[i][j]i(y+1)-d[i][j]j(x+1) \]

所以维护4个二维树状数组,分别存\(d[i][j]\),\(d[i][j]ij\),\(d[i][j]i\),\(d[i][j]j\)即可。

inline void modify(int id,int x,int y,int w)
{
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=m;j+=lowbit(j))
			sh[id][i][j]+=w;
}
inline int query(int id,int x,int y)
{
	int ans=0;
	for(int i=x;i;i-=lowbit(i))
		for(int j=y;j;j-=lowbit(j))
			ans+=sh[id][i][j];
	return ans;
}
inline void modifyz(int a,int b,int zhi)
{
	modify(0,a,b,zhi);modify(1,a,b,zhi*a*b);modify(2,a,b,zhi*a);modify(3,a,b,zhi*b);
}
inline int sum(int a,int b)
{
	return query(0,a,b)*(a*b+a+b+1)+query(1,a,b)-query(2,a,b)*(b+1)-query(3,a,b)*(a+1);
}

例题

P4514 上帝造题的七分钟

板子题,实现上面的区间修改区间查询即可。(注意自己认真推一下修改和查询的位置,不要写错了)

#include <bits/stdc++.h>
using namespace std;
const int N=2050;
int n,m,sh[4][N][N];
char s;
int lowbit(int x){return x&(-x);}
inline void modify(int id,int x,int y,int w)
{
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=m;j+=lowbit(j))
			sh[id][i][j]+=w;
}
inline int query(int id,int x,int y)
{
	int ans=0;
	for(int i=x;i;i-=lowbit(i))
		for(int j=y;j;j-=lowbit(j))
			ans+=sh[id][i][j];
	return ans;
}
inline void modifyz(int a,int b,int zhi)
{
	modify(0,a,b,zhi);modify(1,a,b,zhi*a*b);modify(2,a,b,zhi*a);modify(3,a,b,zhi*b);
}
inline int sum(int a,int b)
{
	return query(0,a,b)*(a*b+a+b+1)+query(1,a,b)-query(2,a,b)*(b+1)-query(3,a,b)*(a+1);
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>s>>n>>m;
	int a,b,c,d,zhi;
	while(cin>>s>>a>>b>>c>>d)
	{
		if(s=='L')
		{
			cin>>zhi;
			modifyz(a,b,zhi);modifyz(c+1,b,-zhi);modifyz(c+1,d+1,zhi);modifyz(a,d+1,-zhi); 
		}
		else
		{
			int ans;
			ans=sum(c,d)-sum(c,b-1)-sum(a-1,d)+sum(a-1,b-1);
			cout<<ans<<'\n'; 
		}
	}
	return 0;
}

Iahub and Xors

类似于与上面的区间修改,区间查询,但是这里不是加,而是异或。
把所有加减和乘变成异或即可。因为\(x \oplus x=0\),最后那个式子不关心具体的数值,只关心奇偶性,只有同奇偶才有贡献,所以多开两维记一下x,y奇偶性,只在奇偶性相同的之间统计答案即可。(也可以仿照上一个题一样写)

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
#define int long long
int n,m,sh[2][2][N][N];
int lowbit(int x){return x&(-x);}
int query(int x,int y)
{
	int res=0;
	for(int i=x;i>0;i-=lowbit(i))
		for(int j=y;j>0;j-=lowbit(j))
			res^=sh[x%2][y%2][i][j];
	return res;
}
void modify(int x,int y,int w)
{
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=n;j+=lowbit(j))
			sh[x%2][y%2][i][j]^=w;
	return ;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int opt,xx1,xx2,yy1,yy2,w;
		cin>>opt>>xx1>>yy1>>xx2>>yy2;
		if(opt==1)
		{
			int ans1=query(xx2,yy2)^query(xx1-1,yy2)^query(xx1-1,yy1-1)^query(xx2,yy1-1);
			cout<<ans1<<'\n';
		}	
		else
		{
			cin>>w;
			modify(xx1,yy1,w);
			modify(xx2+1,yy1,w);
			modify(xx1,yy2+1,w);
			modify(xx2+1,yy2+1,w);
		}
	}
	return 0;
} 
posted @ 2024-11-07 14:36  storms11  阅读(2)  评论(0编辑  收藏  举报