--- tags: Coding --- # 樹 ## 線段樹 (算區間最大值) 迴圈執行方式 像是dfs一樣,先挖到底然後再回頭挖沒挖過的地方 ### 空間 接著要來想一下陣列該開多大,也就是節點編號最多會用到多少。一棵有 $2^i$($i$ 是非負整數)個葉節點的滿二元樹,它的非葉節點會有 $2^i-1$ 個,因此它的總節點數是 $2^{i+1}-1$。線段樹的葉節點數和序列長度是一樣的,如果這個數字是 $n$,那麼葉節點會有 $n$ 個,最後一層填滿的話,應該會有 $2^{\lceil\log_2n\rceil}$ 個葉節點,因此整棵樹會有 $2^{\lceil\log_2n\rceil+1}-1$ 個節點。 如果 $n=2^i+a$,$0<a<2^i$,那麼 $2^{\lceil\log_2n\rceil}=2^{i+1}$,$2^{\lceil\log_2n\rceil+1}-1=2^{i+2}-1<2^{i+2}$,由此可知,$n \times 4$ 一定會大於總節點數,因此陣列大小通常都會取 $n \times 4$。 ![](https://i.imgur.com/iJ3jqBk.jpg) ### 區間最大值 主程式 ```cpp= #include <bits/stdc++.h> using namespace std; const int N = 6; struct segment_tree { int ch, value; }; int arr[N] = {1, 2, 3, 4, 5, 6}; segment_tree st[4 * N]; // 懶人標記向下壓 void push(int lson, int rson, int index) { st[index].value = st[index].ch; if (st[index].ch > 0) { if (lson > 0) st[lson].ch = st[index].ch; if (rson > 0) st[rson].ch = st[index].ch; } st[index].ch = 0; } // 建樹 void build(int l, int r, int index) { if (l == r) { cout << l; st[index].value = arr[l]; } else { int mid = (l + r) / 2, lson = index * 2, rson = index * 2 + 1; build(l, mid, lson); build(mid + 1, r, rson); st[index].value = max(st[lson].value, st[rson].value); } } // 區間查詢 int query(int a, int b, int l, int r, int index) { int mid = (l + r) / 2, lson = index * 2, rson = index * 2 + 1; if(l==r)lson=rson=-1; push(lson,rson,index);//沒有用到懶標的話這行跟上面那行就不用打 if (a <= l && b >= r) { return st[index].value; } else { if (a > mid) { return query(a, b, mid + 1, r, rson); } else if (b <= mid) { return query(a, b, l, mid, lson); } else { return max(query(a, b, mid + 1, r, rson), query(a, b, l, mid, lson)); } } return -1; } // 單點修改 void change_onepoint(int ch, int pos, int l, int r, int index) { if (pos == l && pos == r) { st[index].value = ch; } else { int mid = (l + r) / 2, lson = index * 2, rson = index * 2 + 1; if (pos > mid) { change_onepoint(ch, pos, mid + 1, r, rson); } else if (pos <= mid) { change_onepoint(ch, pos, l, mid, lson); } st[index].value = max(st[lson].value, st[rson].value); } return; } // 區間改值 利用懶標 void change_interval(int ch, int a, int b, int l, int r, int index) { int mid = (l + r) / 2, lson = index * 2, rson = index * 2 + 1; if (l == r) rson = lson = -1; push(lson, rson, index); if (a <= l && b >= r) { st[index].ch = ch; } else if (a > r || b < l) { return; } else { change_interval(ch, a, b, mid + 1, r, rson); change_interval(ch, a, b, l, mid, lson); int x1 = st[lson].ch == 0 ? st[lson].value : st[lson].ch; int x2 = st[rson].ch == 0 ? st[rson].value : st[rson].ch; st[index].value = max(x1, x2); } return; } void look(int t) { int k = 1; cout << endl; for (int i = 1; i < N * 4; i++) { if (!t) cout << st[i].value << " "; else cout << st[i].ch << " "; if (i == k) { cout << endl; k = k * 2 + 1; } } cout << endl; } signed main() { for (int i = 0; i < 4 * N; i++) { st[i].ch = 0; } build(0, N - 1, 1); // 初始化 建樹 look(0); cout << query(2, 4, 0, N - 1, 1) << endl; change_onepoint(11, 3, 0, N - 1, 1); // 改一個值 look(0); change_interval(13, 0, 1, 0, N - 1, 1); look(0); look(1); cout << query(4, 7, 0, N - 1, 1); return 0; } ``` **犯蠢紀錄** if (l == r) rson = lson = -1;打成if (a == l && b == r) rson = lson = -1;原本是子葉不用下推,卻寫成到想要的區域沒有下推。 ## Bit tree (Fenwick tree 算區間和) 定義lowbit(x)為x&(-x) bit[x]中存arr[x-lowbit(x)+1]到arr[x]所有數的和。 update原理: 每一次update只更新有包含到區間。例如改變位置13,則有包含的區間為(13),(13\~14),(1\~16)。還好有一個特別的方法,x加上x&(-x)就可以剛好從13到14,14到16。x&(-x)其實就是取最右邊的1與其後面的0,例如0100100取完就是0000100 ![](https://i.imgur.com/QpcIhd7.jpg) ![](https://i.imgur.com/ldRItDI.jpg) ![](https://i.imgur.com/fF1VSZu.png) [圖片取自](https://yuihuang.com/binary-indexed-tree/) ```cpp= void update(int index,int val){ while(index<=N){ bit[index]+=val; index+=index&(-index); } } ``` query查詢原理: 由下往上加,如果查詢x前綴和。剛好可以用x-x&(-x)來把依序相加。例子中13的前綴和就是bit[13]+bit[12]+bit[8]。 ![](https://i.imgur.com/jJbIu6d.jpg) 查詢 ```cpp= int query(int index){ int val=0; while(index>0){ val+=bit[index]; index-=index&(-index); } return val; } ``` 初始化就是從原本給的數據中一個一個給update更新。 初始化 ```cpp= for(int i=1;i<=N;i++){ update(i,arr[i-1]); } ``` 主程式 ```cpp= const int N=9; int arr[N]={1,2,3,4,5,6,7,8,9}; int bit[N]; void update(int index,int val){ while(index<=N){ bit[index]+=val; index+=index&(-index); } } int query(int index){ int val=0; while(index>0){ val+=bit[index]; index-=index&(-index); } return val; } int main(){ for(int i=1;i<=N;i++){ update(i,arr[i-1]); } for(int i=1;i<=N;i++){ cout<<bit[i]<<" "; } cout<<endl<<query(4); return 0; } ```