線段樹學習筆記

KS_Fszha發表於2024-07-04

目錄

  1. 樸素線段樹
  2. 權值線段樹

樸素線段樹

時間 \(O(n \log n )\) ,空間開 \(4\) 倍,主要操作有 build , query , update , pushup , pushdown 。

公式

\[lc=p<<1 \]

\[rc=(p<<1)|1 \]

易錯點

  1. 判斷是否完全覆蓋區間,大於等於和小於等於的方向:ln<=tr[p].l&&rn>=tr[p].r
  2. query 與 update 都要 pushdown 。
  3. update 與 build 都要 pushup 。
  4. pushdown 與 update 時,注意給子節點加懶標記是用 += 而不是 = 。
  5. 注意初始化時要建樹 : build(1,1,n) ,最好一開始就寫上,防止忘記。
  6. pushdown 操作儘可能放在函式最前面,防止忘記。
  7. 線段長度為 r-l+1
  8. mid 應取 (l+r)>>1
  9. query 作為有返回值的函式,一樣要用 long long 型別。

模板

#include <bits/stdc++.h>
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
const int N=100005;
ll a[N];
struct node{
	int l,r;
	ll sum,add=0;
}tr[4*N];
void pushup(int p)
{
	tr[p].sum=0;
	if(lc<4*N)tr[p].sum+=tr[lc].sum;
	if(rc<4*N)tr[p].sum+=tr[rc].sum;
}
void pushdown(int p)
{
	if(tr[p].add!=0)
	{
		if(lc<4*N)
		{
			tr[lc].add+=tr[p].add;
			tr[lc].sum+=(tr[lc].r-tr[lc].l+1)*tr[p].add;
		}
		if(rc<4*N)
		{
			tr[rc].add+=tr[p].add;
			tr[rc].sum+=(tr[rc].r-tr[rc].l+1)*tr[p].add;
		}
	}
	tr[p].add=0;
}
void build(int p,int ln,int rn)
{
	tr[p].l=ln,tr[p].r=rn,tr[p].add=0;
	if(ln==rn)
	{
		tr[p].sum=a[ln];
		return;
	}
	int mid=(ln+rn)>>1;
	build(lc,ln,mid);
	build(rc,mid+1,rn);
	pushup(p);
}
ll query(int p,int ln,int rn)
{
	pushdown(p);
	if(ln<=tr[p].l&&rn>=tr[p].r)return tr[p].sum;
	int mid=(tr[p].l+tr[p].r)>>1;
	ll nsum=0;
	if(ln<=mid)nsum+=query(lc,ln,rn);
	if(rn>=mid+1)nsum+=query(rc,ln,rn);
	return nsum;
}
void update(int p,int ln,int rn,ll k)
{
	pushdown(p);
	if(ln<=tr[p].l&&rn>=tr[p].r)
	{
		tr[p].add+=k;
		tr[p].sum+=(tr[p].r-tr[p].l+1)*k;
		return;
	}
	int mid=(tr[p].l+tr[p].r)>>1;
	if(ln<=mid)update(lc,ln,rn,k);
	if(rn>=mid+1)update(rc,ln,rn,k);
	pushup(p);
}
int n,m;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	build(1,1,n);
	while(m--)
	{
		int op;
		cin>>op;
		if(op==1)
		{
			ll x,y,k;
			cin>>x>>y>>k;
			update(1,x,y,k);
		}
		else
		{
			ll x,y;
			cin>>x>>y;
			cout<<query(1,x,y)<<endl;
		}
	}
	return 0;
}

相關文章