线段树-简单线段树模板
簡單線段樹
2021年7月30
線段樹是干什么的?
更新,維護,查詢某區間的某項值,如區間和等。
線段樹的原理
與樹狀數組類似,線段樹通過直接建樹來保存區間的結點,如圖:
如何建圖?
下面以求序列區間和為例:
當存放一個數字時,它需要將每個子區間的值返回到父節點之上,依次返回直到最大的區間上。此時,一個區間接收到的值就是它所有子區間值的和。
由于每個區間的大小都為1<<i,0<=i<=n,因此,我們構建的樹是一顆二叉樹。按行進行標號,易得第i個區間的兩個子區間標號分別為i * 2與i * 2 + 1。
依據這個性質,易想到使用遞歸方式進行存圖。代碼如下:
int built(int i, int l, int r){tre[i].l=l;tre[i].r=r;//更新第i個區間的左右邊界if(l==r) return tre[i].sum=a[i];//若區間大小為1,則更新值后直接返回int mid=(l+r/2);return tre[i].all+=built(i*2,l,mid)+built(i*2+1,mid+1,r);//其區間和等于左右子樹的區間和,更新后返回 }單點更新
對于每次查找,只需要找到其位置,更新即可
void onemodify(int i,int pos,int k){if (tre[i].r<l || tre[i].l>r)return;if (tre[i].l >= l && tre[i].r <= r) {tre[i].all += k;}modify(i * 2, l, r, k);modify(i * 2 + 1, l, r, k); }如何對整個區間進行修改?
給出區間l與r,要求區間內每個點加k。從樹上進行遍歷,對每個在區間l-r內的點進行更新值。
void modify(int i, int l, int r, int k) {if (tre[i].r<l || tre[i].l>r)return;//不在區間內,直接返回if (tre[i].l <= l && tre[i].r >= r) {tre[i].all += k*(min(r-l,tre[i].r - tre[i].l) +1);//更改區間在目前結點內,更新最大可更新值}modify(i * 2, l, r, k);modify(i * 2 + 1, l, r, k); }但是,這種更新方式,其復雜度是O(nlog(n)),再加上m次更新,其復雜度達到了O(nmlog(n)),比直接枚舉還要多了log級別的時間復雜度。
如何將其優化到O(log(n))的復雜度?
增加一個標記:懶惰標記
此標記意味著對于此區間內所有子區間的結點,都有增加了k。
void modify(int i, int l, int r, int k){if (tre[i].r<l || tre[i].l>r)return;tre[i].all += k * (min(r, tre[i].r) - max(l, tre[i].l) + 1);//兩區間交集就是需要更新值的部分if ((tre[i].l >= l && tre[i].r <= r)) {tre[i].lazy += k;return;}modify(i * 2, l, r, k);modify(i * 2 + 1, l, r, k); }此時,就不用查找到每一個長度為1的區間了。
注:此時需在建圖時將tre中的lazy初始化為0.
模板樣例
第一行輸入三個數n,m,q,分別為數組大小,修改次數及查詢次數,隨后一行有n個數字,分別是a1…n,第3行到第2+m行,每行三個數,分別為區間邊界及每個數修改的值,接下來q行,每行兩個數,分別為要查詢區間的左右邊界。
每次查詢,輸出一行數字,代表區間和。
輸入樣例:
8 4 7 1 5 2 3 6 9 7 2 2 3 2 3 5 7 3 3 1 4 6 8 1 8 3 3 3 3 2 5 4 8 6 8 5 7輸出樣例:
85 12 12 58 65 26 45代碼
#include<iostream> #include<algorithm> #include<stdio.h> #include<string> #include<string.h>using namespace std;struct Tree {int l, r;int all;int lazy; }tre[1005];int n, m, q; int sic[1005], ans; string row;int builtre(int i, int l, int r) {tre[i].l = l; tre[i].r = r; tre[i].lazy = 0;if (l == r)return tre[i].all=sic[l];int mid = (l + r) / 2;tre[i].all += builtre(i * 2, l, mid);tre[i].all += builtre(i * 2 + 1, mid + 1, r);return tre[i].all; }void modify(int i, int l, int r, int k) {if (tre[i].r<l || tre[i].l>r)return;tre[i].all += k * (min(r, tre[i].r) - max(l, tre[i].l) + 1);if ((tre[i].l >= l && tre[i].r <= r)) {tre[i].lazy += k;return;}modify(i * 2, l, r, k);modify(i * 2 + 1, l, r, k); }int solve(int i, int l, int r,int lazy) {if (tre[i].l >= l && tre[i].r <= r) {ans += tre[i].all;ans += (tre[i].r - tre[i].l + 1) * lazy;return ans;}if (tre[i].l > r || tre[i].r < l)return 0;if (tre[i].r >= l) solve(i * 2, l, r,lazy + tre[i].lazy);if (tre[i].l <= r) solve(i * 2 + 1, l, r, lazy + tre[i].lazy);return ans; }int main() {cin >> n >> m >> q;for (int i = 0; i < n; i++){cin >> sic[i + 1];}builtre(1, 1, n);int a, b, k;while (m--) {cin >> a >> b >> k;modify(1, a, b, k);}while (q--) {cin >> a >> b;ans = 0;cout << solve(1, a, b, tre[1].lazy) << endl;}return 0; }后練習代碼
#include<iostream> #include<algorithm> #include<stdio.h> #include<string> #include<string.h> typedef long long ll;using namespace std;int n, m; int sic[100005];struct P {int l, r;int key, lazy; }tre[100005];int built(int p, int l, int r) {tre[p].l = l; tre[p].r = r;if (l == r)return tre[p].key = sic[l];int mid = (l + r >> 1);return tre[p].key = built(p * 2, l, mid) + built(p * 2 + 1, mid + 1, r); }void insert(int p, int l, int r, int key) {if (tre[p].r<l || tre[p].l>r)return;if (tre[p].l >= l && tre[p].r <= r) {tre[p].lazy += (tre[p].r-tre[p].l+1)*key; return;}tre[p].lazy += (1+min(r - l, min(tre[p].r - l, r - tre[p].l))) * key;insert(2 * p, l, r, key); insert(2 * p + 1, l, r, key); }int findkey(int p, int l, int r) {if (tre[p].r<l || tre[p].l>r)return 0;if (tre[p].l >= l && tre[p].r <= r) return tre[p].key + tre[p].lazy;return findkey(2 * p, l, r) + findkey(2 * p + 1, l, r); }int main() {cin >> n;for (int i = 1; i <= n; i++){cin >> sic[i];}built(1, 1, n);while (1) {int a, l, r, k;cin >> a;if (!a)break;cin >> l >> r;if (a == 2) {cin >> k;insert(1, l, r, k);}elsecout << findkey(1, l, r) << endl;}return 0; }總結
以上是生活随笔為你收集整理的线段树-简单线段树模板的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 风之大陆料理配方是什么
- 下一篇: 字典树-01字典树基础