線段樹

線段樹用於對數據的更新和查詢,主要優勢體現在對段的處理上。如將數組a[]從a[i]-a[j]的元素均加上b,要做j-i+1次,而有一個段表示a[i]-a[j]的話,就直接將這個段的和sum加上b*(j-i+1)即可。同理,查詢段的和也只需一次。

線段樹的原理就是將[1,n]分解成若干子區間,通過對其少量子區間進行修改統計實現對該區間快速的修改統計,用於統計的對象需符合區間加法,如求和,求最值,公因數。

時間複雜度:預處理O(n),查詢、更新操作O(logn);空間複雜度:O(n)。

線段樹的父節點區間平均分割到左右子樹中,對於包含n個葉子節點的二叉樹,它一定有n-1個非葉節點,總共2n-1個節點,因此存儲線段是需要的空間複雜度是O(n)。

結點定義

struct node {
	int l;    //線段左端點
	int r;    //線段右端點
	int value;    //線段的值,這裏指線段中各元素和
	int add;        //lazy_tag
}tree[4*N];

 

利用數組存儲線段樹(有效空間爲2*N-1,實際空間爲滿二叉樹的結點數,即2^(log(2*N-1)+1)-1=4*N-3),tree數組大小爲4*N。其中tree[1]爲根結點,tree[2*i]、tree[2*i+1]分別爲tree[i]的左右孩子結點。

創建線段樹

void build(int v, int l, int r)
{
	tree[v].l = l;
	tree[v].r = r;
	if (l == r)    //葉子結點
	{
		tree[v].value = a[r];
		return;
	}
	int mid = (l + r) / 2;
	build(v << 1, l, mid);
	build(v << 1 | 1, mid + 1, r);
	Push_up(v)    //由子結點更新父節點
}
void Push_up(int v)
{
	tree[v].value = tree[v << 1].value + tree[v << 1 | 1].value;
}

Push_up函數根據左右孩子結點的值更新該結點的值,在求最值問題中可寫爲tree[v].value = max(tree[v << 1].value , tree[v << 1 | 1].value)。

區間更新:

區間更新是指更新某個區間內的葉子節點的值,因爲涉及到的葉子節點不止一個,而葉子節點會影響其相應的非葉父節點,那麼回溯需要更新的非葉子節點也會有很多,如果一次性更新完,操作的時間複雜度肯定不是O(logn)。於是線段樹引入延遲標記,記錄這個節點是否進行了某種修改(這種修改操作會影響其子節點),對於任意區間的修改,我們先按照區間查詢的方式將其劃分成線段樹中的節點,然後修改這些節點的信息,並給這些節點標記上代表這種修改操作的標記。在修改和查詢的時候,如果我們到了一個節點v,並且決定考慮其子節點,那麼我們就要看節點v是否被標記,如果有,就要按照標記修改其子節點的信息,並且給子節點都標上相同的標記,同時消掉節點p的標記。

void Push_down(int v)
{
    tree[v << 1].add += tree[v].add;//可能多次標記沒有傳遞,需+=
    tree[v << 1 | 1].add += tree[v].add;
    tree[v << 1].value += tree[v].add*(tree[v << 1].r - tree[v << 1].l + 1);
    tree[v << 1|1].value += tree[v].add*(tree[v << 1|1].r - tree[v << 1|1].l + 1);
    tree[v].add = 0;
}
void update(int v, int L, int R, int delta)
{//L:左端點,R:右端點 delta:增量
	if (tree[v].l == L&&tree[v].r == R)    //找到需要更新的線段,記錄增加的值add
	{
		tree[v].value += delta*(R - L + 1);
		tree[v].add += delta;
		return;
	}
	if (tree[v].add)	Push_down(v);    //延遲標記向下傳遞給子結點
	int mid = (tree[v].l + tree[v].r) / 2;
	if (R <= mid)	update(v << 1, L, R, delta);//所找範圍在該結點的左半段
	else
	{
		if (L > mid)	update(v << 1 | 1, L, R, delta);//在右半段
		else
		{//橫跨左右兩段
			update(v << 1, L, mid, delta);
			update(v << 1 | 1, mid + 1, R, delta);
		}
	}
	Push_up(v);    //回溯時修改父節點
}

 

區間查詢:

int query(int v, int L, int R)
{
	if (tree[v].l == L&&tree[v].r == R)
	{//找到該段分區
		return tree[v].value;
	}
	if (tree[v].add)	Push_down(v);//延遲標記向下傳遞給子節點
	int mid = (tree[v].l + tree[v].r) / 2,ans;
	if (R <= mid)	ans=query(v << 1, L, R);//在左半段
	else
	{
		if (L > mid)	ans=query(v << 1 | 1, L, R);//在右半段
		else
		{//橫跨左右兩段
			ans=query(v << 1, l, mid)+ query(v << 1 | 1, mid + 1, R);
		}
	}
	return ans;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章