【筆記/模板】樹狀陣列

ThySecret發表於2024-11-04

原理解釋

樹狀陣列是一種透過字首和和差分的思想所進行的維護陣列,從而以 \(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;
}

相關文章