(萌新第一次發文,請大佬指正)php
要了解樹狀數組,首先須要瞭解它是用來作什麼的.
那麼:node
如圖:
忽然一看可能難以理解,那麼是什麼把它們聯繫起來的呢?
接下來介紹lowbit函數c++
這裏用到了補碼的原理:數組
即負數的補碼爲其二進制絕對值取反+1,而當其與其絕對值取&操做時獲得的剛好就是其二進制最後一位1的位置函數
例如:00001000(8)的負數補碼爲11111000(-8) 與操做後爲00001000優化
而lowbit(8)的值就爲 00001000;this
則 lowbit函數代碼spa
inline int lowbit(int x) { return x & -x; }
這裏就稍微偏個題順便介紹一下inline(內聯函數)code
C++關鍵字,在函數聲明或定義中函數返回類型前加上關鍵字inline,便可以把函數指定爲內聯函數。
在c/c++中,爲了解決一些頻繁調用的小函數大量消耗棧空間(棧內存)的問題,特別的引入了inline修飾符,表示爲內聯函數。
此關鍵字僅是對編譯器的一種建議,並不是強制.對於內容較短且無循環的代碼,inline的使用能夠增長函數運行的效率,但其效率的增長是以代碼長度爲代價,因此,僅在短且無循環的函數前可使用,其餘狀況避免使用.blog
那麼lowbit函數的用處是什麼呢.
讓咱們以二進制思惟稍微注意一下能夠發現,每一個序號的二進制數的後導0的個數,恰爲其包含的數組的深度(自底向上的),設其爲d,那麼其包含的範圍就是包括它向前的2^d個數.如100(4)爲從100向前4個原數組中的數的和,而110(6)是從110向前2個原數組中的數的和.
咱們令原數組爲a[],樹狀數組爲tree[].
這時,咱們能夠理解tree數組中存儲的究竟是什麼數據,
那麼對於以上問題模型,咱們該如何求解呢.
咱們要更新原數組中某一個數,那麼就得更新樹狀數組中全部包含它的數據,
那麼,都有哪組數據包含它呢,也就是尋找這個子節點的全部根節點的特色.
這時,就又輪到咱們的lowbit函數上場了,x的全部父節點只需x + lowbit(x)
就能夠獲得.
那麼update函數的代碼(時間複雜度O(logn)):
//x爲須要修改的節點,v爲a[x]增長的值,n是樹狀數組的最大範圍. void update(int x,int v) { for(;x <= n;x += lowbit(x)) tree[x] += v; }
對於樹狀數組tree,咱們能夠了解其存儲的值實質上是某一區間的前綴和.
咱們查詢某一段區間(x,y)的值,就只須要求出y的前綴和減去x-1的前綴和
這樣獲得的,就是區間(x,y)的和.
那麼(1,x)區間的前綴和怎麼求呢,仍然是lowbit函數,只須要作一遍更新的逆過程,
即計算tree數組(x->1)的和就能夠求出全部不重合的區間前綴和.
爲何tree(x->1)就是該區間的前綴和?
在二進制的視角下,x的數位上每個1,都表明其一段域內的值,
那麼,每一個1都必然與其餘1的範圍不重合例如:tree[110] = tree[100] + tree[10]
如此,query的代碼就很明瞭了.
int query(int x) { int res = 0; for(;x > 0;x -= lowbit(x)) res += tree[x]; return res; }
板子題連接(luoguP3374)
到此爲止,咱們講解的就是PUIQ模型,即點更新,段查詢.
怎麼樣代碼是否是短到懷疑人生,這在賽場上寫起來不是舒舒服服?
那麼,接下來的IUPQ模型,就須要一個切入點來完成操做了.
當咱們想要進行段更新時,那麼若是仍然用上面的代碼,就不得不添加一個for循環,
此時update函數的時間複雜度會上升到O(nlogn),那麼,有沒有什麼方法來優化操做呢?解決方案就是--差分
差分,也就是定義一個差分數組b來存儲a數組的差值,而tree做爲數組b的樹狀數組.
即:
b[1] = a[1] b[2] = a[2] - a[1] b[4] = a[4] - a[1] tree[1] = b[1] tree[2] = b[2] + tree[1] tree[4] = tree[2] + tree[3] + b[4]
那麼這時的a數組值該如何獲得呢?
對於b數組,a[n]
就等於b[1] +...+b[n]
而query函數恰好就是用來求b[n]的前綴和,也就是a[n]的值.
那麼更新操做呢,對於差分數組b,若咱們想要更新[l,r]
範圍內的值+v,那麼咱們只須要將b[l]
更新爲b[l] + v
就表明着a[l] - a[n]
全部數都+1,
而在b[r+1]
處將+v的效果消除,即b[r+1] - v
那麼如何將這個操做更新在tree數組中,就是上文update的事情了.
即只需更新b[l]+v,b[r+1]-v便可
update(l,v); update(r+1,-v);
複雜度一樣爲O(logn)
板子題連接(luoguP3368)
問題模型,給定n個整數,求出逆序整數對數(即a[u] > a[v] && u < v
的整數對)
看見題目咱們就能夠寫出O(n2)的暴力for來求解,可是如何將其用樹狀數組來優化到O(nlogn)呢
這裏用到桶排序的思想.
首先讓問題簡單化一點,讓n個整數a[i]均小於等於100.
那麼咱們就能夠開tree[105]的數組,每次讀入更新一個數時,只需update(a[i],1)
此時,比a[i]大的數字就是須要更新的逆序對對數,即query(a[100]) - query(a[i])
由於當前共讀入了i個數,那麼求值亦可簡化爲i - query[a[i]]
也就是讀入一輪+每次查詢就可獲得總共的逆序對對數.時間複雜度O(nlogn)
那麼當數據足夠大時,咱們的數組存不下的時候呢,這時候,就輪到咱們的核心思想,離散化出場了.
對於離散化,我的理解就是因爲想要利用桶數組,故將數據相對縮小(保證相對大小不變)到能夠開到的數組那麼大而減少空間需求.
那麼到底該如何實現,請看以下代碼:
離散化核心代碼: struct node { int v;//數值自己 int order;//原序列的的下標 }a[500005]; int dis[500005]; //用來存儲原數第i個數的order下標是什麼 sort(a,a+n,cmp); //注意須要由大到小排 for(int i = 1;i <= n;++i) dis[a[i].order] = i;
原理很簡單就只是按a[i].v的大小重排,並從新賦予他們相對大小不變,總體縮小的新的a[i].v
具體代碼以下:(HDU2689)
#include <bits/stdc++.h> #define mem(n) memset(n,0,sizeof(n)) using namespace std; int n; struct node{ int val,order; bool operator < (const node & x) const{ return this->val < x.val; } }a[1005]; int dis[1005]; //差分數組 int tree[1005]; inline int lowbit(int x) { return x & -x; } void update(int x,int v) { for(;x <= n;x +=lowbit(x)) tree[x] += v; } int query(int x) { int res = 0; for(;x > 0;x -= lowbit(x)) res += tree[x]; return res; } int main() { while(~scanf("%d",&n)){ mem(a); mem(dis); mem(tree); for(int i = 1;i <=n;++i){ scanf("%d",&a[i].val); a[i].order = i; } sort(a + 1, a + 1 + n); for(int i = 1;i <= n;++i){ dis[a[i].order] = i; } int cnt = 0; for(int i = 1;i <= n;++i){ update(dis[i],1); cnt += i - query(dis[i]); } printf("%d\n",cnt); } return 0; }