1. 程式人生 > 其它 >2022.4.3 線段樹模板

2022.4.3 線段樹模板

其實還是挺簡單的,剛開始迷惑的只有一點,就是懶惰值的作用。

其實就是讓該節點的子節點暫時不更新值以節約時間的。例如,1—5的區間都加上2,可以只在這個區間對應的區間和上加上(5-1+1)*2.並把2加入到這個節點對應的懶惰值中。當後續想要使用

它的子節點的區間時,比如1—3,就可以將這裡的懶惰值歸零並讓1—3與4—5對應的懶惰值加2,隨後這兩個區間的和也加上這個值*元素的個數就行了。

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define MAXN 1000001
unsigned ll n,m,a[MAXN],ans[MAXN<<2],tag[MAXN<<2];
inline ll ls(ll x)//用於求父親節點x的左兒子
{
return x<<1;//位運算,比用運算子好像是快一點。這裡相當於x*2
}
inline ll rs(ll x)//用於求父親節的x的右兒子
{
return x<<1|1;//同上,相當於x*2+1
}
inline void ans_up(ll p)//更新p節點的ans,即該節點對應區間的和
{
ans[p]=ans[ls(p)]+ans[rs(p)];
}
void build(ll p,ll l,ll r)//建立線段樹。
{
tag[p]=0;//懶惰標誌,可以優化時間
if(l==r)//該情況下說明當前區間只有一個元素,為葉子,真正地賦值。
{
ans[p]=a[l];
return;
}
ll mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
ans_up(p);//在遞歸回來的過程中求出每個節點對應區間的和
}
inline void f(ll p,ll l,ll r,ll k)
{
tag[p]+=k;
ans[p]=ans[p]+k*(r-l+1);//使父節點的懶惰值下分至子節點並更新。
}
inline void push_data(ll p,ll l,ll r)//如果用到一個懶惰標誌已賦值的區間的子區間,就需要把懶惰值下發。
{
ll mid=(l+r)>>1;
if(tag[p])//我自己加的,大概能優化一點點時間叭~
{
f(ls(p),l,mid,tag[p]);
f(rs(p),mid+1,r,tag[p]);
tag[p]=0;
}
}
void change_data(ll nl,ll nr,ll l,ll r,ll p,ll k)//用於更改區間值
{
if(nl<=l&&nr>=r)
{
ans[p]+=k*(r-l+1);
tag[p]+=k;
return ;
}
push_data(p,l,r);
ll mid=(l+r)>>1;
if(nl<=mid)
change_data(nl,nr,l,mid,ls(p),k);
if(nr>mid)
change_data(nl,nr,mid+1,r,rs(p),k);
ans_up(p);//更新區間和
}
ll ssum(ll nl,ll nr,ll l,ll r,ll p)//用於輸出區間和
{
ll sum=0;
if(nl<=l&&nr>=r)
return ans[p];
ll mid=(l+r)>>1;
push_data(p,l,r);//下放懶惰值
if(nl<=mid)
sum+=ssum(nl,nr,l,mid,ls(p));
if(nr>mid)
sum+=ssum(nl,nr,mid+1,r,rs(p));
return sum;
}
int main()
{
ll x,y,k,flag;
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
build(1,1,n);
while(m--)
{
scanf("%lld",&flag);
if(flag==1)
{
scanf("%lld%lld%lld",&x,&y,&k);
change_data(x,y,1,n,1,k);
}
else
{
scanf("%lld%lld",&x,&y);
printf("%lld\n",ssum(x,y,1,n,1));
}
}
return 0;
}