線段樹是乙個神奇的東西,可以o(n)建樹,o(logn)修改、查詢,維護乙個區間的性質。但是線段樹維護的序列一定是一維的,如果我要維護乙個「二維」的結構呢?就比如說,維護乙個矩陣中子矩陣的和。簡單地說就是給你乙個**,每次用「圈出」乙個矩形的部分讓你求它所有元素的和。
沒有學過線段樹的同學們一定要先學一下線段樹一定要先學習一下,再來看這篇部落格。
解決這個問題自然要從靜態(一維)區間和得出靈感。靜態一維區間和我們用的方法是求「字首和」,我們可以用o(n)的時間複雜度求出乙個pre陣列,pre[i]表示閉區間[1,i]對應元素的和。如果我想要求區間[i,j]的區間和,用計算pre[j]-pre[i-1]就可以o(1)解決問題。
同學們可以嘗試著把這個理論推廣到二維,我們可以去維護乙個二維「字首和」來解決這個問題。
區域「i」的矩陣和,就相當於是區域「i+ii+iii+iv」的和減去區域「iii+iv」、減去區域「iii+ii」、再加上區域「iii」。
如果我們用sum(i,j,k,l)表示區域「i」,那麼就有sum(i,j,k,l)=sum(1,j,1,l)-sum(1,i,1,l)-sum(1,j,1,k)+sum(1,i,1,k)。這樣我們就把所有的資料表示成了乙個「二維字首」的形式了。我們可以用pre(i,j)表示sum(1,i,1,j),就有sum(i,j,k,l)=pre(j,l)-pre(i,l)-pre(j,k)+pre(i,k)。
pre(i,j)如何求解呢?顯然可以使用和sum同樣的方法:pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+a[i][j](a表示原陣列)。
請看偽**:
init:
for i = 1 to n
for j = 1 to m
pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+a[i][j]
query:
sum(i,j,k,l)=pre[j][l]-pre[i][l]-pre[j][k]+pre[i][k]
二維線段樹,每乙個節點對應乙個子矩陣(根節點代表整體),每個節點有四個兒子節點,分別表示它的「左上,左下,右上,右下」四個部分,例如下圖:
當然,如果邊長不是二的整數次冪也是可以這樣二分的:
這樣我們就得到了乙個建樹的方法:
build(root,left,right,up,down)
if 確定到唯一元素
root.sum=這個元素
else
if 這個區域只有一列
root.左上子=新結點
root.左下子=新結點
int mid=(up+down)/2
build(root.左上子,left,right,up,mid)
build(root.左下子,left,right,mid+1,down)
root.sum=root.左上子.sum+root.左下子.sum
else
if 這個區間只有一行
root.左上子=新結點
root.右上子=新結點
int mid=(left+right)/2
build(root.左上子,left,mid,up,down)
build(root.右上子,mid+1,right,up,down)
root.sum=root.左上子.sum+root.右上子.sum
else
root.左上子=新結點
root.左下子=新結點
root.右上子=新結點
root.右下子=新結點
int midlr=(left+right)/2
int midud=(up+down)/2
build(root.左上子,left,midlr,up,midud)
build(root.左下子,left,midlr,midud+1,down)
build(root.右上子,midlr+1,right,up,midud)
build(root.右下子,midlr+1,right,midud+1,down)
root.sum=root.左上子.sum+root.左下子.sum+root.右上子.sum+root.右下子.sum
查詢還是比較簡單的(因為沒有lazy,不懂的回去複習線段樹!):
query(root,left,right,up,down)
if root == null//這樣當查詢null結點時可以直接忽略掉,不會re
return
0return query(root.左上子,left,right,up,down)+
query(root.左下子,left,right,up,down)+
query(root.右上子,left,right,up,down)+
query(root.右下子,left,right,up,down)
然後是正經的**:
struct node
}ns[1048576];
int newnode=1;//當前亟待申請的節點
#define lst(root) (ns[root].l)
#define rst(root) (ns[root].r)
#define ust(root) (ns[root].u)
#define dst(root) (ns[root].d)//表示乙個結點的區間範圍
#define luch(root) (ns[root].luch)
#define ruch(root) (ns[root].ruch)
#define ldch(root) (ns[root].ldch)
#define rdch(root) (ns[root].rdch)//表示乙個結點的四個兒子
#define sum(root) (ns[root].sum)//這些define可以使**更好理解,但實際上沒什麼必要
int a[101][101];
void build(int root,int l,int r,int u,int d)
else
if(l==r)
else
}int ask(int root,int l,int r,int u,int d)
理論上來講,**與普通線段樹是極其相似的。
趕稿匆忙,如有謬誤,望同學們諒解。
二維線段樹
二維線段樹一般用樹套樹的方式實現,每個外層線段樹的節點對應一顆內層線段樹,整個線段樹存放在乙個二維陣列中。二維線段樹 poj2155 include include include include include include include include include include inc...
二維線段樹
二維線段樹矩陣區間查詢最大值 矩陣求和預處理後o 1 就能算出來,不用線段樹,除非有修改操作 先第一維在第二維,注意建樹有個順序問題,應該讓第一維度的先建完然後再建第二個維度 具體看 include include include using namespace std const int maxn...
二維線段樹
一維線段樹用來維護一維的空間,即乙個線段。二維線段樹用來維護二維的空間,即乙個矩形。二維線段樹的每個結點都是一棵一維線段樹,所以結構體陣列要開二維,再加上線段樹本身的性質,會占用很大記憶體,要儘量減少結構體內儲存的值的個數和長度,考慮到每個節點表示的線段的左右端點可以作為函式引數,所以不再儲存在結構...