線段樹區間更新模板——懶標記(lazy-tag)的運用

一、定義

  • 在線段樹中會遇到區間更新的情況,例如 在區間求和問題中,令[a,b]區間內的值全部加c,若此時再採用單點更新的方法,就會耗費大量時間,這個時候就要用到懶標記來進行區間更新了。

  • 懶標記(lazy-tag),又叫做延遲標記,舉例說明。

當前結點對應區間[l, r]待更新區間[a, b]
 當 a ≤ l ≤ r ≤ b,即 [l, r]∈[a,b]時,不再向下更新,僅更新當前結點,並在該結點加上懶標記,當必須得更新/查詢該結點的左右子結點時,再利用懶標記的記錄向下更新(pushdown)——懶標記也要向下傳遞,然後移除該結點的懶標記。

這樣就不用每次都更新到葉子結點,減少了大量非必要操作,從而優化時間複雜度。



二、模板

以下均以在區間求和問題中,令[a,b]區間內的值全部加c爲例

laz[rt]:表示其該結點rt對應區間[l, r]內的所有數據均要加上laz[rt]

1. 建樹

建樹操作與無懶標記的線段樹無異,但要記得對懶標記數組進行初始化

int a[maxn],t[4*maxn],laz[4*maxn];

void build(int rt,int l,int r)
{
	if(l==r)
	{
		t[rt]=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	t[rt]=t[rt<<1]+t[rt<<1|1]; 
}

2.向下更新

void pushdown(int rt,int l,int r)    //對結點rt向下更新一層
{
	if(laz[rt]==0)        //laz[rt]==0直接返回
		return;
	//向下更新
	//令左右子結點加上 laz[rt]*區間的數量
	//令左右子結點的懶標記加上 laz[rt]
	int mid=(l+r)>>1;
	t[rt<<1]+=laz[rt]*(mid-l+1);     
	laz[rt<<1]+=laz[rt];
	t[rt<<1|1]+=laz[rt]*(r-mid);
	laz[rt<<1|1]+=laz[rt];
	laz[rt]=0;            //懶標記置零
}

3.區間更新

void updata(int rt,int l,int r,int a,int b,int c)
{
	if(a<=l&&r<=b)         //當前區間[l,r]∈待更新區間[a,b]
	{
		t[rt]+=c*(r-l+1);  //更新當前結點,並加上懶標記
		laz[rt]+=c;         
		return;	           //返回
	}
	pushdown(rt,l,r);      //因爲還要向下一層遍歷,所有要先更新下一層
	int mid=(l+r)>>1;
	if(b<=mid)             //待更新區間在中點(包括中點)左側 —— 向左遍歷
		updata(rt<<1,l,mid,a,b,c);
	else if(a>mid)         //待更新區間在中點(不包括中點)右側 —— 向右遍歷
			updata(rt<<1|1,mid+1,r,a,b,c);
	else                   //否則
	{
		updata(rt<<1,l,mid,a,b,c);
		updata(rt<<1|1,mid+1,r,a,b,c);
	}
	t[rt]=t[rt<<1]+t[rt<<1|1];      //不要漏了這條
}

4. 區間查詢

帶懶標記的查詢操作,就是當查詢時還需向下遍歷的時候,要先把此處懶標記向下更新。

int query(int rt,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr)     //當前區間[l,r]∈待查詢區間[ql,qr]
		return t[rt];
	if(r<ql||l>qr)       //區間無重合
		return 0;
	//區間部分重合
	pushdown(rt,l,r);    //先向下更新
	int mid=(l+r)>>1;
	return query(rt<<1,l,mid,ql,qr)+query(rt<<1|1,mid+1,r,ql,qr);
}


三、模板例題

注意結果可能會超出int,要用long long存儲

#include<cstdio>
#include<algorithm> 
#define LL long long
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e5+7;
int N,Q;
LL a[maxn],t[4*maxn],laz[4*maxn];
void build(int rt,int l,int r)
{
	if(l==r)
	{
		t[rt]=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	t[rt]=t[rt<<1]+t[rt<<1|1]; 
}
void pushdown(int rt,int l,int r)
{
	if(laz[rt]==0)
		return;
	int mid=(l+r)>>1;
	t[rt<<1]+=laz[rt]*(mid-l+1);
	laz[rt<<1]+=laz[rt];
	t[rt<<1|1]+=laz[rt]*(r-mid);
	laz[rt<<1|1]+=laz[rt];
	laz[rt]=0; 
}
void updata(int rt,int l,int r,int a,int b,LL c)
{
	if(a<=l&&r<=b)
	{
		t[rt]+=c*(r-l+1);
		laz[rt]+=c;
		return;	
	}
	pushdown(rt,l,r);
	int mid=(l+r)>>1;
	if(b<=mid)
		updata(rt<<1,l,mid,a,b,c);
	else if(a>mid)
		updata(rt<<1|1,mid+1,r,a,b,c);
	else
	{
		updata(rt<<1,l,mid,a,b,c);
		updata(rt<<1|1,mid+1,r,a,b,c);
	}
	t[rt]=t[rt<<1]+t[rt<<1|1]; 
}
LL query(int rt,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr)
		return t[rt];
	if(r<ql||l>qr)
		return 0;
	pushdown(rt,l,r);
	int mid=(l+r)>>1;
	return query(rt<<1,l,mid,ql,qr)+query(rt<<1|1,mid+1,r,ql,qr);
}
int main()
{
	scanf("%d %d",&N,&Q);
	for(int i=1;i<=N;i++)
		scanf("%lld",&a[i]);
	build(1,1,N);
	while(Q--)
	{
		getchar();
		char C=getchar();
		int a,b;
		scanf("%d %d",&a,&b);
		if(C=='C')
		{
			LL c;
			scanf("%lld",&c);
			updata(1,1,N,a,b,c);
		}
		else
			printf("%lld\n",query(1,1,N,a,b));
	}
	return 0; 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章