1. 程式人生 > >學習:樹狀數組&線段樹

學習:樹狀數組&線段樹

什麽是 -m 數據規模 https 先來 輸入格式 負數 查詢 ima

先上一道題目:

題目描述

如題,已知一個數列,你需要進行下面兩種操作:

  1. 將某一個數加上x

  2. 求出某區間每一個數的和

輸入輸出格式

輸入格式:

第一行包含兩個整數N、M,分別表示該數列數字的個數和操作的總個數。

第二行包含N個用空格分隔的整數,其中第i個數字表示數列第i項的初始值。

接下來M行每行包含3個整數,表示一個操作,具體如下:

操作1: 格式:1 x k 含義:將第x個數加上k

操作2: 格式:2 x y 含義:輸出區間[x,y]內每個數的和

輸出格式:

輸出包含若幹行整數,即為所有操作2的結果。

輸入輸出樣例

輸入樣例#1:

5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4

輸出樣例#1:

14
16

說明

時空限制:1000ms,128M

數據規模:

對於30%的數據:N<=8,M<=10
對於70%的數據:N<=10000,M<=10000
對於100%的數據:N<=500000,M<=500000

想暴力過?不存在的(底層優化不敢說)。於是,我們需要一種數據結構來進行優化。

樹狀數組

如果我們有一個數組\(a\),我們可以構造一個數組\(C\),使\(C[i]=a[i-2^k+1]+\cdots+a[i]\)\(k\)\(i\)在二進制下末尾\(0\)的個數。

這其實是一個絕妙的想法,因為\(x\)對應的\(2^k\)是十分好求的,我們稱求\(2^k\)的函數為lowbit:

inline LL lowbit(LL x)
{
    return x&(-x);
}

為什麽呢?

首先,我們先來看看什麽是補碼:

一個數字的補碼就是將該數字作比特反相運算(即反碼),再將結果加1。在補碼系統中,一個負數就是用其對應正數的補碼來表示。
如:+8是00001000,而-8就是~8+1=11110111+1=11111000

如果\(x\)的二進制位末尾有\(k\)\(0\),那麽在取時,它們都會變成\(1\),加\(1\)之後,又都變成了\(0\)。又因為\(x\)的二進制位末尾只有\(k\)\(0\),故第\(k+1\)位一定是\(1\),取反後變成\(0\),加\(1\)後,由於進位,又變成了\(1\)

,進位由此停止。與是,這兩個數的二進制位上,除了第\(k+1\)位,其余應該都至少有一個是\(0\)\(k\)以前的為上補碼一定是\(0\),可以後的位上有於取反,必定一位是\(0\),一位是\(1\))。由此可知,得到的答案是\(2^k\)

其實,C數組就是一棵樹狀數組。

樹狀數組的結構如下圖所示:
技術分享圖片

單點修改

代碼

inline void add(LL pla,LL num)
{
    for(; pla<=n; pla+=lowbit(pla))a[pla]+=num;
}

查詢

模板

typedef long long LL;

class bit
{
        LL a[500005];
        inline LL lowbit(LL x)
        {
            return x&(-x);
        }

    public:
        LL n;
        inline void add(LL pla,LL num)
        {
            for(; pla<=n; pla+=lowbit(pla))a[pla]+=num;
        }
        inline LL sum(LL pla)
        {
            LL ans=0;
            for(; pla; pla-=lowbit(pla))ans+=a[pla];
            return ans;
        }
};

學習:樹狀數組&線段樹