一維樹狀數組詳解(萌新的第一篇博客)

(萌新第一次發文,請大佬指正)php

要了解樹狀數組,首先須要瞭解它是用來作什麼的.
那麼:node

樹狀數組的問題模型

  • 單點維護,區間查詢(PUIQ問題)
  • 區間維護,單點查詢(IUPQ問題)
  • 求逆序對問題

先來了解一下樹狀數組的邏輯模型

如圖:
樹狀數組圖示.jpg
忽然一看可能難以理解,那麼是什麼把它們聯繫起來的呢?
接下來介紹lowbit函數c++

lowbit(求二進制數最後一位1的位置)

這裏用到了補碼的原理:數組

即負數的補碼爲其二進制絕對值取反+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數組中存儲的究竟是什麼數據,
那麼對於以上問題模型,咱們該如何求解呢.


更新(update函數)

咱們要更新原數組中某一個數,那麼就得更新樹狀數組中全部包含它的數據,
那麼,都有哪組數據包含它呢,也就是尋找這個子節點的全部根節點的特色.

這時,就又輪到咱們的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;
}

查詢(query函數)

對於樹狀數組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模型,就須要一個切入點來完成操做了.


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;
}
相關文章
相關標籤/搜索