有一類問題,它們的大意都是維護一段序列,給出兩種操作,一種是對一段區間進行整體修改,一類是對一段區間詢問其中元素的極值或者和。
這一類問題通常是使用線段樹來解的,然而,如果操作比較簡單,線段樹的實現也會有不同的方法,這裡以最簡單的一道題poj 3468為例。
第一種方法:線段樹 + lazy標記
我們使用lazy標記維護當前節點的子區間是否有延後更新,我們知道如果對於每一次的區間修改都暴力做的話複雜度是o(n)的,我們想要把它減少到o(logn),於是對於一整段區間都需要修改的情況,我們可以就在這整段區間的代表節點記下這個區間需要改變的值,當我們需要深入這個區間時再將這個資訊下放下取,運用這個思想就降低了大量的冗餘操作,問題就順利解決了。
source:
#include const int nmax = 100000, tmax = 1 << 18;第二種方法:線段樹 + 差分思想long long segflag[tmax + 18], segsum[tmax + 18];
int n, q, m = 1, l, r, h = 1;
char c;
long long k;
void pushdown(int i)
}
void add(int l, int r, long long c)
if ( r & 1) segflag[r ^ 1] += c;
segsum[l << 1] += ldel;
segsum[r << 1] += rdel;
}}long long getsum(int l, int r)
return cnt;
}int main()
return 0;
}
這種方法,相較於前面一種,程式設計複雜度更小,但是思維複雜度相對較大,下面要經過一些數學推理:
假設我們需要維護的原序列為a1, a2…. an,再設乙個新陣列a1, a2 …. an,我們對於這個陣列的定義為:
a[i] = a[i] – a[i – 1] (a[0] = 0)
我們再設這兩個陣列的字首和陣列分別為sa1, sa2 …. san, sa1, sa2, …. san,即:
sa[i] = sigma(j from 1 to i)a[j]
sa[i] = sigma(j from 1 to i)a[j] = a[i]
那麼易得: sa[i] = sigma(j form 1 to i)a[j] = sigma(j from 1 to i)sa[j]
於是我們可知:原陣列a的字首和就是差分後的陣列a的字首和的字首和。
現在讓我們看看進行區間修改之後原陣列和其差分陣列的變化,假設我們將原陣列[l, r]這段區間整體加上乙個值k,那麼
a陣列: a[1],a[2]….a[l], a[l + 1]….a[r – 1], a[r]….a[n]
變成:a[1],a[2]….a[l] + k, a[l + 1] + k,….a[r – 1] + k, a[r] + k,….a[n]
a陣列:a[1], a[2],….a[l], a[l + 1],….a[r – 1], a[r],….a[n]
變成:a[1], a[2],….a[l] + k, a[l + 1],…. a[r – 1], a[r],a[r + 1] - k,….a[n]
是否發現了什麼? 對區間進行修改,原陣列有o(n)級別的位置的修改,而其差分陣列只有兩個數發生了變化!
這就是我們要使用的性質。
現在我們希望利用這個性質快速維護區間資訊:
我們需要求
於是,只要我們能夠快速統計a[i]和ia[i]的區間資訊,就能夠快速統計區間和,又因為對於a陣列我們只需要改動兩個點,所以將乙個線段樹支援區間修改區間查詢的問題轉換成了兩個線段樹支援單點修改區間查詢的問題。
以3468的樣例為例
原陣列 a: 1 2 3 4 5 6 7 8 9 10
差分陣列 a:1 1 1 1 1 1 1 1 1 1
輔助陣列ia[i]:1 2 3 4 5 6 7 8 9 10
q:4 4 ans:(4 + 1 - 4) * (1 + 1 + 1 + 1) – (4) + 4 * 1= 4
q:1 10 ans:(10 – 1 + 1) * 10 – 55 + 10 = 55
q:2 4 ans:(4 – 2 + 1) * 4 – 9 + 2 * 3 = 9
c:3 6 3 a:1 1 4 1 1 1 -2 1 1 1 ia[i]:1 2 12 4 5 6 -14 1 1 1
q:2 4 ans:(4 – 2 + 1) * 7 – 18 + 2 * 6 = 15
問題解決。
source:
#include typedef long long ll;const int nmax = 100000, tmax = 1 << 18;
int n, q;
ll seg[2][tmax + 18];
int a[nmax + 18];
int m = 1;
int l, r, k;
char c;
void inc(int pos, int k)
ll getsum(ll *a, int l, int r)
return rnt;
}int main()
else
printf("%i64d\n", getsum(seg[0], 1, r) * (r + 1 - l) - getsum(seg[1], l, r) + getsum(seg[0], l, r) * l);
}return 0;
}
線段樹 區間修改
我們對於線段樹的區間修改你可以用乙個最傻的辦法迴圈進行單點修改 時間複雜度太高十分麻瓜 所以,我們要用乙個聰明的做法延遲標記 lazy 我們在執行修改指令時,同樣可以在 l pl pr r 的情況下立即返回,只不過在回溯之前向節點p增加乙個標記,標識 該節點曾經被修改過,但其子節點尚未被更新 如果在...
線段樹的區間修改
1 include 2 include3 define lc p 1 該節點的左孩子 4 define rc p 1 1 右孩子5 using namespace std 67 很類似與樹狀陣列 8int n,m 9struct segment tree 10tree 1000000 線段樹陣列 1...
線段樹 區間求和模板 (區間修改)
include include include include include include include include include define mem a memset a,0,sizeof a define mem2 a memset a,1,sizeof a define mod ...