目錄
- 樸素線段樹
- 權值線段樹
樸素線段樹
時間 \(O(n \log n )\) ,空間開 \(4\) 倍,主要操作有 build , query , update , pushup , pushdown 。
公式
\[lc=p<<1
\]
\[rc=(p<<1)|1
\]
易錯點
- 判斷是否完全覆蓋區間,大於等於和小於等於的方向:
ln<=tr[p].l&&rn>=tr[p].r
。 - query 與 update 都要 pushdown 。
- update 與 build 都要 pushup 。
- pushdown 與 update 時,注意給子節點加懶標記是用 += 而不是 = 。
- 注意初始化時要建樹 :
build(1,1,n)
,最好一開始就寫上,防止忘記。 - pushdown 操作儘可能放在函式最前面,防止忘記。
- 線段長度為
r-l+1
。 - mid 應取
(l+r)>>1
。 - 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;
}