二維樹狀陣列及(不會用到的)三維樹狀陣列
二維樹狀陣列及(不會用到的)三維樹狀陣列
前置芝士
一維樹狀陣列(lowbit)
二維樹狀陣列
二維樹狀陣列涉及到兩種基本操作,修改矩陣中的一個點,查詢子矩陣的和
首先是修改點的操作:
void update(int x,int y,int z){ //座標為(x,y)的點增加z
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=n;j+=lowbit(j))
c[i][j]+=z;
}
然後是查詢子矩陣的和,這裡查詢的是從左上角到目標點所形成的矩陣的元素和
int sum(int x,int y){ int ret=0; for(int i=x;i>=1;i-=lowbit(i)) for(int j=y;j>=1;j-=lowbit(j)) ret+=c[i][j]; return ret; }
那麼如果我要查具體的一個子矩陣,就需要給出左上角的點和右下角的點的座標,然後:
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
cout<<sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1)<<endl;
就可以了
下面附上完整的二維樹狀陣列的程式碼:
#include<iostream> using namespace std; const int maxn=1005; const int maxm=1005; int n,m; int q; int a[maxn][maxm]; int c[maxn][maxm]; int lowbit(int x) { return x&(-x); } void update(int x,int y,int z) { for(int i=x;i<=n;i+=lowbit(i)) for(int j=y;j<=m;j+=lowbit(j)) c[i][j]+=z; } int sum(int x,int y) { int ret=0; for(int i=x;i>=1;i-=lowbit(i)) for(int j=y;j>=1;j-=lowbit(j)) ret+=c[i][j]; return ret; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { cin>>a[i][j]; update(i,j,a[i][j]); } cin>>q; while(q--) { int x; cin>>x; if(x==1) { int y,z,w; cin>>y>>z>>w; update(y,z,w); } if(x==2) { int x1,y1,x2,y2; cin>>x1>>y1>>x2>>y2; cout<<sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1)<<endl; } } return 0; }
接下來我們對二維樹狀陣列進行簡單的拓展,將其拓展為修改矩形區間,查詢單點的二維樹狀陣列
其實就是把二維差分的思想引入進去,當然,如果不用樹狀陣列直接用二維差分陣列也是完全可以的,這個時候修改區間變成了O(1),查詢點就變成了O(n),還是需要自己去權衡
二維樹狀陣列的修改和查詢的函式還是完全不用去變的
修改區間就要這麼修改了:
void add(int x1,int y1,int x2,int y2,int w) { update(x1,y1,w); update(x2+1,y2+1,w); update(x2+1,y1,-w); update(x1,y2+1,-w); }
這個東西雖然是類比一維情況得來的,但是你不要去想,去在紙上畫一畫,主對角線端點為正,負對角線端點為負,然後就很顯然了
查詢單點的話直接sum(x,y)即可
這裡給出完整的程式碼:
#include<iostream>
using namespace std;
const int maxn=1005;
const int maxm=1005;
int n,m;
int q;
int a[maxn][maxm];
int d[maxn][maxm];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int y,int z)
{
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=m;j+=lowbit(j))
d[i][j]+=z;
}
void add(int x1,int y1,int x2,int y2,int w)
{
update(x1,y1,w);
update(x2+1,y2+1,w);
update(x2+1,y1,-w);
update(x1,y2+1,-w);
}
int sum(int x,int y)
{
int ret=0;
for(int i=x;i>=1;i-=lowbit(i))
for(int j=y;j>=1;j-=lowbit(j))
ret+=d[i][j];
return ret;
}
int main()
{
cin>>n>>m;
cin>>q;
while(q--)
{
int x;
cin>>x;
if(x==1)
{
int x1,y1,x2,y2,w;
cin>>x1>>y1>>x2>>y2>>w;
add(x1,y1,x2,y2,w);
}
if(x==2)
{
int x,y;
cin>>x>>y;
cout<<sum(x,y)<<endl;
}
}
return 0;
}
最後思考如何區間修改+區間查詢
類比之前一維陣列的區間修改區間查詢(這個部落格沒有),下面這個式子表示的是點(x, y)的二維字首和:
類比一維陣列,統計一下每個出現過多少次。出現了次,出現了次……出現了 次。
那麼這個式子就可以寫成:
把這個式子展開,就得到:
那麼我們要開四個樹狀陣列,分別維護:
,,,
這樣就可以解決上述問題了
程式碼如下:
void update(int x,int y,int z)
{
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=m;j+=lowbit(j))
d1[i][j]+=z;
d2[i][j]+=z*x;
d3[i][j]+=z*y;
d4[i][j]+=z*x*y;
}
void add(int x1,int y1,int x2,int y2,int w)
{
update(x1,y1,w);
update(x2+1,y2+1,w);
update(x2+1,y1,-w);
update(x1,y2+1,-w);
}
int sum(int x,int y)
{
int ret=0;
for(int i=x;i>=1;i-=lowbit(i))
for(int j=y;j>=1;j-=lowbit(j))
ret+=(x+1)*(y+1)*d1[i][j]-(y+1)*d2[i][j]-(x+1)*d3[i][j]+d4[i][j];
return ret;
}
int ask(int x1,int y1,int x2,int y2)
{
return sum(x2,y2)-sum(x2,y1-1)-sum(x1-1,y2)+sum(x1-1,y1-1);
}
模板題
BZOJ 3132(自己上網搜吧)
程式碼
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
const int maxn=2105;
const int maxm=2105;
int n,m;
int a[maxn][maxm],d1[maxn][maxm],d2[maxn][maxm],d3[maxn][maxm],d4[maxn][maxm];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int y,int z)
{
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=m;j+=lowbit(j)){
d1[i][j]+=z;
d2[i][j]+=z*x;
d3[i][j]+=z*y;
d4[i][j]+=z*x*y;
}
}
void add(int x1,int y1,int x2,int y2,int w)
{
update(x1,y1,w);
update(x2+1,y2+1,w);
update(x2+1,y1,-w);
update(x1,y2+1,-w);
}
int sum(int x,int y)
{
int ret=0;
for(int i=x;i>=1;i-=lowbit(i))
for(int j=y;j>=1;j-=lowbit(j))
ret+=(x+1)*(y+1)*d1[i][j]-(y+1)*d2[i][j]-(x+1)*d3[i][j]+d4[i][j];
return ret;
}
int ask(int x1,int y1,int x2,int y2)
{
return sum(x2,y2)-sum(x2,y1-1)-sum(x1-1,y2)+sum(x1-1,y1-1);
}
int main()
{
char op[2];
scanf("%s",op);
cin>>n>>m;
while(scanf("%s",op)!=-1)
{
if(op[0]=='L')
{
int x1,y1,x2,y2,w;
cin>>x1>>y1>>x2>>y2>>w;
add(x1,y1,x2,y2,w);
}
else
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
cout<<ask(x1,y1,x2,y2)<<endl;
}
}
return 0;
}
三維樹狀陣列
怎麼拓展呢?直接在二維樹狀陣列的基礎上加一維就可以了,不用進行任何改動,這裡我們只介紹其中的一種變式,那就是三維樹狀陣列修改區間查詢點
(如果有人出三維樹狀陣列修改區間查詢區間的那種題,直接在二維樹狀陣列修改區間查詢區間的基礎上改,應該不會有這種題的)
下面給出程式碼,著重觀察修改部分就可以了。
#include<iostream>
using namespace std;
const int maxn=105;
const int maxm=105;
const int maxl=105;
int n,m,l;
int q;
int a[maxn][maxm][maxl];
int c[maxn][maxm][maxl];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int y,int z,int w)
{
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=m;j+=lowbit(j))
for(int k=z;k<=l;k+=lowbit(k))
c[i][j][k]+=w;
}
int sum(int x,int y,int z)
{
int ret=0;
for(int i=x;i>=1;i-=lowbit(i))
for(int j=y;j>=1;j-=lowbit(j))
for(int k=z;k>=1;k-=lowbit(k))
ret+=c[i][j][k];
return ret;
}
int main()
{
cin>>n>>m>>l;
cin>>q;
while(q--)
{
int x;
cin>>x;
if(x==1)
{
int x1,y1,z1,x2,y2,z2,w;
cin>>x1>>y1>>z1>>x2>>y2>>z2>>w;
update(x1,y1,z1,w);
update(x1,y2+1,z1,-w);
update(x2+1,y1,z1,-w);
update(x2+1,y2+1,z1,w);
update(x1,y1,z2+1,-w);
update(x1,y2+1,z2+1,w);
update(x2+1,y1,z2+1,w);
update(x2+1,y2+1,z2+1,-w);
}
if(x==2)
{
int x,y,z;
cin>>x>>y>>z;
cout<<sum(x,y,z)<<endl;
}
}
return 0;
}
部分段落來自靜聽風吟。和Lv1_kangdi