数据结构一【树状数组】普通、二维、离线树状数组的(单点修改,单点查询,区间修改,区间查询)模板及应用例题总结
文章目錄
- 樹狀數組
- lowbit
- 線段樹與樹狀數組
- 單點修改
- 區間查詢
- 區間修改
- 區間求和
- 二維樹狀數組
- 離線樹狀數組
- 例題
- POJ:stars
- MooFest
- [SDOI2009]HH的項鏈
- Turing Tree
- Counting Sequences
- Zip-line
樹狀數組
用于快速高效的計算與前綴和相關的信息
lowbit
int lowbit( int i ) { return i & -i; }lowbit\rm lowbitlowbit:返回xxx二進制最低位為111的位置的值
e.g. 40=101000,lowbit(40)=8
線段樹與樹狀數組
因為涉及lowbit\rm lowbitlowbit,所以樹狀數組的下標一定從111開始,而不是000
線段樹用mid=(l+r)>>1進行log?\loglog的優化
樹狀數組的通過±lowbit(i)±\rm lowbit(i)±lowbit(i)進行二進制位的進/退111
時間復雜度同樣都是O(nlog?n)O(n\log n)O(nlogn)
但一般來說樹狀數組的空間都是O(N)O(N)O(N),不會像線段樹有N<<2N<<2N<<2的大空間
線段樹因為其結構原因有更多的應用:優化建圖,線段樹分治.........
但是樹狀數組就比較死板了,就是跟靜態區間/單點掛鉤
那么應用廣點,消耗的代價(更大空間)多點也是可以理解的了
很多情況下樹狀數組和線段樹沒有什么區別,可以互換
單點修改
void add( int i, int val ) {for( ;i <= n;i += lowbit( i ) )t[i] += val; }區間查詢
int query( int i ) {//求的是[1,i]的前綴和int ans = 0;for( ;i;i -= lowbit( i ) ) ans += t[i];return ans; } int query( int l, int r ) {return query( r ) - query( l - 1 ); }一般的寫法都是維護前綴和,所以修改是+lowbit+\rm lowbit+lowbit,查詢是?lowbit-\rm lowbit?lowbit
但有些時候題目反而是跟后綴掛鉤,這個時候有兩種選擇
-
強制后綴轉前綴
每次傳入iii的時候,暴力變成i=n?i+1i=n-i+1i=n?i+1,然后進行add query的操作
-
直接反轉使用樹狀數組
修改直接?lowbit-\rm lowbit?lowbit,查詢+lowbit+\rm lowbit+lowbit
只要維護了意義上的相對即可
區間修改
原序列a1,a2,...,ana_1,a_2,...,a_na1?,a2?,...,an?,定義差分數組ci=ai?ai?1c_i=a_i-a_{i-1}ci?=ai??ai?1?, 則ai=∑j=1icja_i=\sum_{j=1}^ic_jai?=∑j=1i?cj?
那么修改區間[l,r][l,r][l,r],加上www,相當于在clc_lcl?加www,在cr+1c_{r+1}cr+1?減www
這就將區間修改轉化成了兩次的單點修改
達到[l,r][l,r][l,r]區間的數加www的效果
區間求和
∑i=1nai=∑i=1n∑j=1icj=∑i=1n(n?i+1)ci=(c1)+(c1+c2)+...+(c1+c2+...+cn)\sum_{i=1}^na_i=\sum_{i=1}^n\sum_{j=1}^ic_j=\sum_{i=1}^n(n-i+1)c_i \\=(c_1)+(c_1+c_2)+...+(c_1+c_2+...+c_n) i=1∑n?ai?=i=1∑n?j=1∑i?cj?=i=1∑n?(n?i+1)ci?=(c1?)+(c1?+c2?)+...+(c1?+c2?+...+cn?)
=(n+1)?(c1+c2+...+cn)?(c1?1+c2+c2?2+...+cn+cn?n)=(n+1)*(c_1+c_2+...+c_n)-(\overbrace{c_1}^{1}+\overbrace{c_2+c_2}^{2}+...\overbrace{+c_n+c_n}^{n}) =(n+1)?(c1?+c2?+...+cn?)?(c1??1?+c2?+c2??2?+...+cn?+cn??n?)
=(n+1)?∑i=1nci?∑i=1nci?i=(n+1)*\sum_{i=1}^nc_i-\sum_{i=1}^nc_i*i =(n+1)?i=1∑n?ci??i=1∑n?ci??i
所以只需要用兩個樹狀數組,分別維護cic_ici?和ci?ic_i*ici??i即可
LOJ:區間修改區間查詢
#include <cstdio> #define int long long #define maxn 1000005 int n, Q; int a[maxn], t1[maxn], t2[maxn];int lowbit( int x ) { return x & -x; }void modify( int x, int val ) {for( int i = x;i <= n;i += lowbit( i ) )t1[i] += val, t2[i] += val * x; }int query( int x ) {int ans = 0;for( int i = x;i;i -= lowbit( i ) )ans += ( x + 1 ) * t1[i] - t2[i];return ans; }signed main() {scanf( "%lld %lld", &n, &Q );for( int i = 1;i <= n;i ++ ) {scanf( "%lld", &a[i] );modify( i, a[i] - a[i - 1] );}int opt, l, r, x;while( Q -- ) {scanf( "%lld %lld %lld", &opt, &l, &r );if( opt & 1 ) {scanf( "%lld", &x );modify( l, x ), modify( r + 1, -x );}else printf( "%lld\n", query( r ) - query( l - 1 ) );}return 0; }二維樹狀數組
既然樹狀數組是前綴和的工具,那么二維樹狀數組就相當于與二維差分
樹狀數組嵌樹狀數組的感覺,查詢就是用二維差分計算圍成的面積
void modify( int x, int y, int val ) {for( int i = x;i <= n;i += lowbit( i ) )for( int j = y;j <= m;j += lowbit( j ) )t[i][j] += val; } int query( int x, int y ) {int ans = 0;for( int i = x;i;i -= lowbit( i ) )for( int j = y;j;j -= lowbit( j ) )ans += t[i][j];return ans; }modify( x1, y1, k ); query( x2, y2 ) - query( x2, y1 - 1 ) - query( x1 - 1, y2 ) + query( x1 - 1, y1 - 1 );離線樹狀數組
離線樹狀數組求區間不同數的個數/值和
都是將詢問按照l,r\rm l,rl,r排序,然后記錄iii的上一個/下一個位置
將指針撥到詢問的端點處,刪去上一個位置/加入下一個位置
從而做到111的個數差,滿足不同數只記錄一次的要求,自然樹狀數組就能維護
例題的最后兩題就是如此,看代碼比較清晰能夠理解
例題
POJ:stars
POJ2352
題目已經保證了yyy遞增,那么樹狀數組維護xxx,每次查詢比xxx小的星星有多少個即可
#include <cstdio> #include <iostream> using namespace std; #define maxn 32005 int n, N; int ans[maxn], t[maxn], x[maxn], y[maxn];int lowbit( int i ) { return i & -i; }void modify( int i ) {for( ;i <= N;i += lowbit( i ) ) t[i] ++; }int query( int i ) {int ret = 0;for( ;i;i -= lowbit( i ) ) ret += t[i];return ret; }int main() {scanf( "%d", &n );for( int i = 1;i <= n;i ++ ) {scanf( "%d %d", &x[i], &y[i] );x[i] ++, y[i] ++, N = max( N, x[i] );//x,y值域包含0 樹狀數組不能從0開始 所以整體+1}for( int i = 1;i <= n;i ++ )ans[query( x[i] )] ++, modify( x[i] );for( int i = 0;i < n;i ++ )printf( "%d\n", ans[i] );return 0; }MooFest
POJ1990
按vvv排序,維護兩個樹狀數組,一個是小于當前位置的位置和,一個是大于當前位置的位置和,這樣就避免了距離帶的絕對值
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define int long long #define maxn 20005 struct node {int val, pos;node(){}node( int Val, int Pos ) {val = Val, pos = Pos;} }cow[maxn]; struct Node {int cnt, sumd;Node(){}Node( int Cnt, int Sumd ) {cnt = Cnt, sumd = Sumd;} }t1[maxn], t2[maxn]; int n, N;int lowbit( int i ) { return i & -i; }void modify1( int i, int val ) {for( ;i <= N;i += lowbit( i ) )t1[i].cnt ++, t1[i].sumd += val; }void modify2( int i, int val ) {for( ;i;i -= lowbit( i ) )t2[i].cnt ++, t2[i].sumd += val; }Node query1( int i ) {Node ans( 0, 0 );for( ;i;i -= lowbit( i ) )ans.cnt += t1[i].cnt, ans.sumd += t1[i].sumd;return ans; }Node query2( int i ) {Node ans( 0, 0 );for( ;i <= N;i += lowbit( i ) )ans.cnt += t2[i].cnt, ans.sumd += t2[i].sumd;return ans; }bool cmp( node x, node y ) { return x.val < y.val; } signed main() {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ ) {scanf( "%lld %lld", &cow[i].val, &cow[i].pos );N = max( cow[i].pos, N );}N ++;sort( cow + 1, cow + n + 1, cmp );int ans = 0;for( int i = 1;i <= n;i ++ ) {Node t = query1( cow[i].pos );ans += ( cow[i].pos * t.cnt - t.sumd ) * cow[i].val;t = query2( cow[i].pos );ans += ( t.sumd - t.cnt * cow[i].pos ) * cow[i].val;modify1( cow[i].pos, cow[i].pos );modify2( cow[i].pos, cow[i].pos );}printf( "%lld\n", ans );return 0; }[SDOI2009]HH的項鏈
Luogu1972
離線樹狀數組求區間不同數個數
將詢問按lll排序,對于每個位置iii記錄下一個與該位置值相等的位置,每一次到iii就把下一次的位置加進去
詢問區間左端點以前的自然都要加,這樣區間查詢相減,就知道下一次的位置在不在區間內,就恰好為111
#include <cstdio> #include <algorithm> using namespace std; #define maxn 1000005 struct node {int l, r, id; }q[maxn]; int n, m; int a[maxn], t[maxn], lst[maxn], nxt[maxn], ans[maxn]; bool vis[maxn];void read( int &x ) {x = 0; char s = getchar();while( s < '0' or s > '9' ) s = getchar();while( '0' <= s and s <= '9' ) {x = ( x << 1 ) + ( x << 3 ) + ( s ^ 48 );s = getchar();} }int lowbit( int i ) { return i & -i; }void add( int i ) {for( ;i < maxn;i += lowbit( i ) ) t[i] ++; }int query( int i ) {int ret = 0;for( ;i;i -= lowbit( i ) ) ret += t[i];return ret; }int main() {read( n );for( int i = 1;i <= n;i ++ ) read( a[i] );read( m );for( int i = 1;i <= m;i ++ ) read( q[i].l ), read( q[i].r ), q[i].id = i;sort( q + 1, q + m + 1, []( node x, node y ) { return x.l < y.l; } );for( int i = 1;i <= n;i ++ )if( ! vis[a[i]] ) add( i ), vis[a[i]] = 1;for( int i = n;i;i -- ) {if( ! lst[a[i]] ) nxt[i] = maxn;else nxt[i] = lst[a[i]];lst[a[i]] = i;}int pos = 1;for( int i = 1;i <= m;i ++ ) {while( pos < q[i].l ) add( nxt[pos] ), pos ++;ans[q[i].id] = query( q[i].r ) - query( q[i].l - 1 );}for( int i = 1;i <= m;i ++ )printf( "%d\n", ans[i] );return 0; }Turing Tree
HDU3333
離線樹狀數組求區間不同數的和
與不同數的個數一致的思路,這里值域比較大,就記錄下標
按照rrr排序也可
#include <map> #include <cstdio> #include <algorithm> using namespace std; #define maxn 30005 #define maxm 100005 #define int long long struct node {int l, r, id; }q[maxm]; map < int, int > lst; int T, n, m; int a[maxn], t[maxn], ans[maxm];int lowbit( int i ) { return i & -i; }void add( int i, int val ) {for( ;i <= n;i += lowbit( i ) ) t[i] += val; }int query( int i ) {int ret = 0;for( ;i;i -= lowbit( i ) ) ret += t[i];return ret; }signed main() {scanf( "%lld", &T );while( T -- ) {scanf( "%lld", &n );for( int i = 1;i <= n;i ++ )scanf( "%lld", &a[i] ), t[i] = 0;scanf( "%lld", &m );for( int i = 1;i <= m;i ++ )scanf( "%lld %lld", &q[i].l, &q[i].r ), q[i].id = i;sort( q + 1, q + m + 1, []( node x, node y ) { return x.r < y.r; } );lst.clear();int j = 1;for( int i = 1;i <= m;i ++ ) {for( ;j <= q[i].r;j ++ ) {if( lst[a[j]] ) add( lst[a[j]], -a[j] );add( j, a[j] ), lst[a[j]] = j;}ans[q[i].id] = query( q[i].r ) - query( q[i].l - 1 );}for( int i = 1;i <= m;i ++ )printf( "%lld\n", ans[i] );}return 0; }Counting Sequences
HDU3450
很簡單的dpdpdp轉移都能想到
dpi:dp_i:dpi?: 最后一位選iii的完美子序列個數,cnti:icnt_i:icnti?:i前面與iii距離不超過ddd的個數
則dpi=∑j,∣xi?xj∣≤ddpj+cntidp_i=\sum_{j,|x_i-x_j|\le d}dp_j+cnt_idpi?=∑j,∣xi??xj?∣≤d?dpj?+cnti?
因為規定個數至少要是222,但jjj成為第一個和后面的iii組成新111個子序列是不會被計算在dpjdp_jdpj?里面的,所以單獨開一個cntcntcnt記錄
用值域樹狀數組維護,查詢query(x+d)?query(x?d?1)\rm query(x+d)-query(x-d-1)query(x+d)?query(x?d?1)
但是這道題的難度就在于值域過大,樹狀數組內存根本開不下
把nnn個位置離散化,查詢就直接找離散化數組中最大的不超過該值的
用upper_bound查就可以了
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define maxn 100005 #define mod 9901 int n, d, m; int t1[maxn], t2[maxn], x[maxn], pos[maxn];int lowbit( int i ) { return i & -i; }void add( int i, int val ) {for( ;i <= m;i += lowbit( i ) )t1[i] ++, t2[i] += val; }pair < int, int > query( int i ) {int ans1 = 0, ans2 = 0;for( ;i > 0;i -= lowbit( i ) ) ans1 += t1[i], ans2 += t2[i];return make_pair( ans1, ans2 ); }int find( int p ) {return upper_bound( pos + 1, pos + m + 1, p ) - pos - 1; }int main() {while( ~ scanf( "%d %d", &n, &d ) ) {for( int i = 1;i <= n;i ++ )scanf( "%d", &x[i] ), pos[i] = x[i], t1[i] = t2[i] = 0;sort( pos + 1, pos + n + 1 );m = unique( pos + 1, pos + n + 1 ) - pos - 1;int ans = 0;for( int i = 1;i <= n;i ++ ) {pair < int, int > r = query( find( x[i] + d ) );pair < int, int > l = query( find( x[i] - d - 1 ) );pair < int, int > t = make_pair( r.first - l.first, r.second - l.second );t.first = ( t.first + mod ) % mod;t.second = ( t.second + mod ) % mod;ans = ( ans + t.first + t.second ) % mod;x[i] = lower_bound( pos + 1, pos + m + 1, x[i] ) - pos;add( x[i], ( t.first + t.second ) % mod );}printf( "%d\n", ans );}return 0; }Zip-line
CF650D
預處理以iii開始/結尾的最長上升子序列li/ril_i/r_ili?/ri?
查詢時,考慮答案有三種變化
-
往iii前找比新值xxx小的hjh_jhj?的最大值rjr_jrj?,往iii后找比xxx大的hjh_jhj?的最大值ljl_jlj?
lj+rj’+1l_j+r_j’+1lj?+rj?’+1 把三段拼起來,如果比答案大,就輸出
-
iii一定在LIS\rm LISLIS中,答案就?1-1?1
-
iii可以不在LIS\rm LISLIS中,答案不變
e.g. 1 2 2 3,2是可以不在LIS\rm LISLIS中的,因為作用與另外一個等效
那現在就是求出哪些數是一定在LIS\rm LISLIS中
- 求出包含iii的最長上升子序列,如果與LIS\rm LISLIS相等,那么iii可以在LIS中
- 對于一個可以在LIS的數iii,如果iii前面≥i\ge i≥i的數可以在LIS中,iii可以不在LIS中
- 對于一個可以在LIS的數iii,如果iii后面≤i\le i≤i的數可以在LIS中,iii可以不在LIS中
對于xxx,求ai<xa_i< xai?<x的最大值bib_ibi?,離散化樹狀數組解決
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define maxn 800005 struct node { int pos, val, id, l, r; }q[maxn]; int n, m, N, ans; int t[maxn], x[maxn], h[maxn], St[maxn], Ed[maxn], ret[maxn], cnt[maxn];int lowbit( int i ) { return i & -i; }void add( int i, int val ) {for( ;i <= N;i += lowbit( i ) ) t[i] = max( t[i], val ); }int query( int i ) {int ans = 0;for( ;i;i -= lowbit( i ) ) ans = max( ans, t[i] );return ans; }int main() {scanf( "%d %d", &n, &m );for( int i = 1;i <= n;i ++ )scanf( "%d", &h[i] ), x[++ N] = h[i];for( int i = 1;i <= m;i ++ ) {scanf( "%d %d", &q[i].pos, &q[i].val );q[i].id = i, x[++ N] = q[i].val;}sort( x + 1, x + N + 1 );N = unique( x + 1, x + N + 1 ) - x - 1;for( int i = 1;i <= n;i ++ )h[i] = lower_bound( x + 1, x + N + 1, h[i] ) - x;for( int i = 1;i <= m;i ++ )q[i].val = lower_bound( x + 1, x + N + 1, q[i].val ) - x;for( int i = 1;i <= n;i ++ )Ed[i] = query( h[i] - 1 ) + 1, add( h[i], Ed[i] );memset( t, 0, sizeof( t ) );for( int i = n;i;i -- )St[i] = query( N - h[i] ) + 1, add( N - h[i] + 1, St[i] );memset( t, 0, sizeof( t ) );for( int i = 1;i <= n;i ++ )ans = max( ans, St[i] + Ed[i] - 1 );for( int i = 1;i <= n;i ++ )if( Ed[i] + St[i] - 1 == ans )++ cnt[Ed[i]];sort( q + 1, q + m + 1, []( node x, node y ) { return x.pos < y.pos; } );int j = 1;for( int i = 1;i <= m;i ++ ) {for( ;j < q[i].pos;j ++ ) add( h[j], Ed[j] );q[i].l = query( q[i].val - 1 );}memset( t, 0, sizeof( t ) );j = n;for( int i = m;i;i -- ) {for( ;j > q[i].pos;j -- ) add( N - h[j] + 1, St[j] );q[i].r = query( N - q[i].val );}for( int i = 1;i <= m;i ++ )if( q[i].l + q[i].r + 1 > ans )ret[q[i].id] = q[i].l + q[i].r + 1;for( int i = 1;i <= m;i ++ )if( ! ret[q[i].id] ) {if( Ed[q[i].pos] + St[q[i].pos] - 1 == ans and cnt[Ed[q[i].pos]] == 1 and q[i].l + q[i].r + 1 < ans )ret[q[i].id] = ans - 1;else ret[q[i].id] = ans;}for( int i = 1;i <= m;i ++ )printf( "%d\n", ret[i] );return 0; }總結
以上是生活随笔為你收集整理的数据结构一【树状数组】普通、二维、离线树状数组的(单点修改,单点查询,区间修改,区间查询)模板及应用例题总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一二三系列之优先队列、st表——Batt
- 下一篇: 一起爬山什么梗 你知道吗