簡單理解一維樹狀數組區間求和+修改


FBI WARNING

在閱讀前,請先弄懂單點修改+區間查詢和區間修改+單點查詢。

近日,本萌新在學習了樹狀數組后,在某度上尋找了各大大佬的區間修改+區間查詢的博客。

發現了高一年級無法理解的奇怪的操作...

於是乎,在我的不懈努力(手動模擬)之下,終於弄懂了這個樹狀數組區間求和修改的奧♂義。

那么首先我們假設一個數組a,里面是我們的所有數。

讓我們回憶一下區間修改需要干什么,對了!維護差分數組

所以我們再來一個數組d,是我們數組a的差分數組。

所以a[i] = d[1] + d[2] + .. + d[i]; (下標從一開始)

因此我們只需要維護一個差分數組就可以了。

讓我們再回憶一下區間查詢該怎么做,對了!前綴和!

那么肯定有人要問了,我堂堂正正一個差分數組,前綴和怎么搞?

現在我們來看一下:

  • a[1] = d[1];

  • a[2] = d[1] + d[2];

  • a[3] = d[1] + d[2] + d[3];

  • a[4] = d[1] + d[2] + d[3] + d[4];

停!打住!夠了。

假設我們要求sum[4] = a[1] + a[2] + a[3] + a[4]。

那么我們的式子就會變成;

sum[4] = d[1] + d[1] + d[2] + d[1] + d[2] + ........ + d[4];

超麻煩啊喂!

我們回去再看一看a[1] - a[4]那四行。這次我們豎着看。

有沒有豁然開朗的趕腳!

sum[4] = d[1] ∗ 4 + d[2] ∗ 3 + d[3] ∗ 2 + d[4] ∗ 1;

接下來再看....

sum[4] = d[1] ∗ 4 + d[2] ∗ 3 + d[3] ∗ 2 + d[4] ∗ 1;

= (d[1] ∗ 5 + d[2] ∗ 5 + .. d[4] ∗ 5) - d[1] ∗ 1 + d[2] ∗ 2 + .. d[4] ∗ 4;

所以如果我們要求sum[i];

sum[i] = (i + 1)∗(d[1] + d[2] + .. d[i]) - (d[1]∗1 + d[2] ∗ 2 + .. d[i] ∗ i);

現在我們嘗試將這個式子與前綴和聯系起來:

看這個 (d[1] + d[2] + .. d[i])

這不是 d[i]的前綴和嗎?!

再看這個 (d[1]∗1 + d[2] ∗ 2 + .. d[i] ∗ i)

如果我們考慮再弄一個數組d2。

d2[i] = d[i] * i;

帶入一下 (d2[1] + d2[2] + .. d2[i])

就變成了d2的前綴和!

所以我們將a數組差分后放進d數組里,初始化d2[i] = d[i] * i

我們就可以弄兩個樹狀數組,一個維護d,一個維護d2

接下來放 P3372的代碼。大家也可以去做一下(用樹狀數組哦)

首先需要這倆

LL d_tr[100005],d2_tr[100005]; // 分別是維護d和d2的樹狀數組 

這是更新操作

void add(int x,LL v) // 在x的位置加上v
{
	for(int i = x;i <= n;i += lowbit(i)){
		d_tr[i] += v;
		d2_tr[i] += x * v;
	} 
	// 因為 d2_tr[x] += d_tr[x] * x;
	// d_tr[x] += v;所以我們要更新d2_tr[x] = (d_tr[x] + v) * x;
	// 就相當於 d2_tr[x] += x * v;
	// 然后向上更新 
	// 循環中的i是用來代替向上更新的x的,不懂得可以手動模擬一下~ 
}

這是查詢

LL sum(int x) // 查詢前綴和
{
	long long ans = 0;
	for(int i = x;i > 0;i -= lowbit(i)){
		ans += (x+1) * d_tr[i] - d2_tr[i];
	}
	// 根據我們推出來的東西求前綴和
	// 不懂的話,手動模擬依舊是個好辦法 
	return ans;
}

輸入時的初始化

for(int i = 1;i <= n;i++){
		scanf("%lld",&a[i]);
		add(i,a[i] - a[i-1]); // 將差分后的值放入樹狀數組 
	}

主函數中的更新記得寫成這樣

	add(x,v);		//區間更新操作,差分的知識 
	add(y+1,-v);

主函數中的查詢記得寫成這樣

	LL ans = sum(y) - sum(x-1);	//前綴和之差算區間和 
	printf("%lld\n",ans);

總代碼

#include <iostream>
#include <cstdio>
#define LL long long
using namespace std;
LL a[100005],n,m;
LL d_tr[100005],d2_tr[100005]; // 分別是維護d和d2的樹狀數組 

int lowbit(int x){
	return x & (-x);
}

void add(int x,LL v) // 在x的位置加上v
{
	for(int i = x;i <= n;i += lowbit(i)){
		d_tr[i] += v;
		d2_tr[i] += x * v;
	} 
	// 因為 d2_tr[x] += d_tr[x] * x;
	// d_tr[x] += v;所以我們要更新d2_tr[x] = (d_tr[x] + v) * x;
	// 就相當於 d2_tr[x] += x * v;
	// 然后向上更新 
	// 循環中的i是用來代替向上更新的x的,不懂得可以手動模擬一下~ 
}

LL sum(int x) // 查詢前綴和 主函數里要寫成sum(y) - sum(x-1);
{
	long long ans = 0;
	for(int i = x;i > 0;i -= lowbit(i)){
		ans += (x+1) * d_tr[i] - d2_tr[i];
	}
	// 根據我們推出來的東西求前綴和
	// 不懂的話,手動模擬依舊是個好辦法 
	return ans;
}

int main()
{
	scanf("%lld%lld",&n,&m);
	for(int i = 1;i <= n;i++){
		scanf("%lld",&a[i]);
		add(i,a[i] - a[i-1]); // 將差分后的值放入樹狀數組 
	}
	while(m--){
		LL v;
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		if(op == 1){
			scanf("%lld",&v);
			add(x,v);		//區間更新操作,差分的知識 
			add(y+1,-v);
		}
		else {
			LL ans = sum(y) - sum(x-1);	//前綴和之差算區間和 
			printf("%lld\n",ans);
		}
	}
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM