學習:樹狀數組&線段樹
先上一道題目:
題目描述
如題,已知一個數列,你需要進行下面兩種操作:
將某一個數加上x
求出某區間每一個數的和
輸入輸出格式
輸入格式:
第一行包含兩個整數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\)
其實,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;
}
};
學習:樹狀數組&線段樹