線段樹學習筆記

Kelojonle發表於2024-12-02

線段樹學習筆記

對線段樹的介紹

  1. 線段樹是一種樹形資料結構,屬於二叉樹
  2. 一棵線段樹的每個結點都可以用區間\([l,r]\)來表示。線段樹的葉結點表示的區間為\([l,l]\)\([r,r]\)
  3. 線段樹支援單點修改,區間修改,區間和/區間最值等的查詢。
  4. 對於線段樹的區間修改一般採用延遲修改方式
  5. 線段樹去掉最後一層後是一棵滿二叉樹

線段樹的建樹

線段樹的表示

因為線段樹是一棵二叉樹,所以可以採用與二叉堆一致的表示法:

編號為p的結點的

  1. 左結點的編號為\(p\times2\)
  2. 右結點的編號為\(p\times2+1\)

類似二叉堆,線段樹是一個結構體陣列。

C++ - 6 行
struct SegmentTree
{
    int l,r;// 表示該結點代表的區間
    int dat;//區間[l,r]的最大值
    int add;//延遲修改標記
}

線段樹的結點

為了避免最最讓人難受的RE,所以考慮一棵線段樹的最大結點數。

設現在需要表示區間\([1,n]\),使得構造的線段樹為一棵滿二叉樹。

由線段樹的性質得知,該二叉樹有\(n\)個葉結點,則總結點數為\(n+n\div2+n\div4+···+2+1\),即\(4n-1\)

因為我的個人習慣,我一般會不處理一個陣列的第一個元素,即下標為\(0\)的元素。

C++ - 27 行
#include<bits/stdc++.h>
using namespace std;
struct SegmentTree
{
    int l,r;// 表示該結點代表的區間
    int dat;//區間[l,r]的最大值
    int add;//延遲修改標記
} t[4*1000001];
int va[114514];//區間是陣列下標區間,這是線段樹葉結點實際代表的值
void bulid(int l,int r,int p)// 結點的陣列下標為p,表示區間[l,r]
{
    t[p].l = l;//代表區間初始化
    t[p].r = r;
    if(l == r)//葉結點
    {
        t[p].dat = va[l];
        return;// 如果你想RE,建議刪去這行
    }
    int mid = (t[p].l + t[p].r) / 2;
    bulid(l,mid,p*2);// 左子結點
    bulid(mid+1,r,p*2+1);//右子結點
    t[p].dat = max(t[p*2].dat,t[p*2+1].dat);//自下往上更新資料
}
int main()
{
    //略
}

線段樹的區間修改&延遲修改

區間修改

對於訪問到的每一個結點,如果修改範圍完全包括了它,那麼直接修改+打延遲修改標記。否則:

  1. 若修改範圍與該結點的左結點有重合,則遞迴修改左結點

  2. 若修改範圍與該結點的右結點有重合,則遞迴修改右結點

C++ - 20 行
#include<bits/stdc++.h>
using namespace std;
void change(int l,int r,int add,int p)//add 為區間修改所要加上的值
{
    if(l <= t[p].l && t[p].r <= r)
    {
        t[p].add += add;
        t[p].dat += add;
        return ;
    }
	spread(p);
    int mid = (t[p].l + t[p].r) / 2;
    if(l <= mid)
    {
        change(l,r,add,p*2);
    }
    if(mid < r)
    {
        change(l,r,add,p*2+1);
    }
}

延遲修改

延遲修改的含義並不是當訪問到該結點時在進行修改,而是在訪問到它時,它已經被修改,若其不是葉結點,則給它的兩個子結點,進行對應修改並打上標記,代表該結點已被修改,但其子結點沒有被修改。

C++ - 16 行
#include<bits/stdc++.h>
using namespace std;
void spread(int p)// p為父結點下標
{
    if(t[p].add)
    {
        t[p*2].add = t[p*2+1].add = t[p].add;//給子結點打標記
        t[p*2].dat += t[p].add;
        t[p*2+1].dat += t[p].add;//因為是最大值,所以直接加上就行
        t[p].add = 0;//刪除標記
    }
}
int main()
{
    //略
}

線段樹的區間查詢

對於訪問到的每一個結點,如果查詢範圍完全包括了它,那麼直接返回。否則:

  1. 若查詢範圍與該結點的左結點有重合,則遞迴查詢左結點

  2. 若查詢範圍與該結點的右結點有重合,則遞迴查詢右結點

C++ - 18 行
#include<bits/stdc++.h>
using namespace std;
long long  ask(int l,int r,int p)
{
	if(l <= t[p].l && t[p].r <= r)
	{
		return t[p].dat;
	}
	spread(p);
	int mid = (t[p].l + t[p].r) / 2;
	long long dat=0x8000000000000000;
	if(l <= mid)
	{
		dat=max(dat,ask(l,r,p*2));
	}
	if(mid < r)
	{
		dat=max(dat,ask(l,r,p*2+1));
	}
	return dat;
}

The end

完整程式碼
#include<bits/stdc++.h>
using namespace std;
struct SegmentTree
{
	int l,r;// 表示該結點代表的區間
	long long  dat;//區間[l,r]的最大值
	long long add;//延遲修改標記
} t[4*1000001];
long long va[114514];//區間是陣列下標區間,這是線段樹葉結點實際代表的值
void bulid(int l,int r,int p)// 結點的陣列下標為p,表示區間[l,r]
{
	t[p].l = l;//代表區間初始化
	t[p].r = r;
	if(l == r)//葉結點
	{
		t[p].dat = va[l];
		return;// 如果你想RE,建議刪去這行
	}
	int mid = (l + r) / 2;
	bulid(l,mid,p*2);// 左子結點
	bulid(mid+1,r,p*2+1);//右子結點
	t[p].dat=max(t[p*2].dat , t[p*2+1].dat );//自下往上更新資料
}
void spread(int p)// p為父結點下標
{
	if(t[p].add )
	{
		t[p*2].add += t[p].add; 
		t[p*2+1].add += t[p].add;//給子結點打標記
		t[p*2].dat +=  t[p].add;
		t[p*2+1].dat +=  t[p].add;//因為是最大值,所以直接加上就行
		//刪除標記
	}
	t[p].add = 0;
}
void change(int l,int r,int add,int p)//add 為區間修改所要加上的值
{
	if(l <= t[p].l && t[p].r <= r)
	{
		t[p].add += add;
		t[p].dat += add;
		return ;
	}
	spread(p);
	int mid = (t[p].l + t[p].r) / 2;
	if(l <= mid)
	{
		change(l,r,add,p*2);
	}
	if(mid < r)
	{
		change(l,r,add,p*2+1);
	}
	t[p].dat=max(t[p*2].dat , t[p*2+1].dat );
}
long long  ask(int l,int r,int p)
{
	
	if(l <= t[p].l && t[p].r <= r)
	{
		return t[p].dat;
	}
	spread(p);
	int mid = (t[p].l + t[p].r) / 2;
	long long dat=0x8000000000000000;
	if(l <= mid)
	{
		dat=max(dat,ask(l,r,p*2));
	}
	if(mid < r)
	{
		dat=max(dat,ask(l,r,p*2+1));
	}
	return dat;
}
int main()
{
	int n,m;
	cin>>n>>m;
	bulid(1,n,1);//建樹
	for(int i=1,op,l,r,add;i<=m;i++)//op為操作型別,[l,r]表示區間,add為修改加上的值
	{
		cin>>op>>l>>r;
		if(op==1)
			change(l,r,add,1);
		if(op==2)
		{
			cin>>add;
			cout<<ask(l,r,1)<<endl;
		}

	}
}

相關文章