原理解釋
樹狀陣列是一種透過字首和和差分的思想所進行的維護陣列,從而以 \(O(\log n)\) 的時間複雜度進行修改和查詢。
一共有四種修改和查詢的方式,分別是:
-
單點修改 \(+\) 區間詢問
-
區間修改 \(+\) 單點詢問
-
單點修改 \(+\) 區間詢問
-
(二維)區間修改 \(+\) 區間詢問
其中利用樹狀陣列可以高效而簡潔地處理第二、三個問題,第四個問題推薦使用【模板/筆記】線段樹。
查詢
查詢可以計算左右區間的總和,透過它們的字首和相減求出區間和。透過上圖可以發現,如果要求 \(sum_{7}\) 的值,可以發現 \(sum_{7} = tree_{7} + tree_{6} + tree_{4}\),而 \(7,6,4\) 之間有什麼關聯呢?
將其轉化為二進位制可以看出:\(7=(111)_{2},6=(110)_{2},4=(100)_{2}\),它們二進位制中 \(1\) 的個數是不斷減少的,而且減少的是最後一位,我們只需要在每一次加後將最後一位的 \(1\) 變為 $ 0 $ 即可。
維護
和查詢一樣,將一個數 \(tree_{i}\) 加上 \(k\),只需要將 \(i\) 的二進位制的最後的 \(1\) 加上 \(1\),直到加的數超過了樹狀陣列的大小。
可以定義一個函式 lowbit(x)
來求出一個二進位制數的最後一位 \(1\):
int lowbit(int x)
{
return x & -x; // 一個數的原碼與上補碼
}
程式碼實現
單點修改
void update(int x, int d) // 在第x點上加上d
{
for (int i = x; i <= n; i += lowbit(x))
tr[i] += d;
}
單點查詢
int sum(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
res += tr[i];
return res;
}
單點修改 + 查詢 主函式
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++)
{
cin >> x;
update(i, x);
}
while (m--)
{
int op;
cin >> op;
if (op == 1)
{
int x, k; cin >> x >> k;
update(x, k);
}
else
{
int x, y; cin >> x >> y;
cout << sum(y) - sum(x - 1) << '\n';
}
}
return 0;
}
區間修改 + 單點查詢 主函式
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
update(i, a[i] - a[i - 1]);
}
while (m--)
{
int op;
cin >> op;
if (op == 1)
{
int x, y, k; cin >> x >> y >> k;
update(x, k);
update(y + 1, -k);
}
else
{
int x; cin >> x;
cout << sum(x) << '\n';
}
}
return 0;
}