洛谷 P3372 線段樹1
同一層節點所代表的區間,加起來是個連續的區間
對於每一個非葉結點所表示的結點[a,b],其左兒子表示的區間為[a,(a+b)/2],右兒子表示的區間為[(a+b)/2+1,b](除法去尾取整)
葉子節點表示的區間長度為1. 使用線段樹可以快速的查詢某一個節點在若干條線段中出現的次數,時間複雜度為O(logN)。而未優化的空間複雜度為2N,實際應用時一般還要開4N的陣列
②n=9
二.線段樹的構造與實現
1.建樹與維護
根據線段樹的性質,我們可以得到一個節點的左兒子和右兒子的表示方法(上面有提QwQ)
inline ll ls(ll x)//左兒子
{
return x<<1;//相當於x*x
}
inline ll rs(ll x)//右兒子
{
return (x<<1)|1;//相當於x*2+1
}
於是就有了維護的程式碼:
inline void push_up_sum(ll p)//向上維護區間和
{
ans[p]=ans[ls(p)]+ans[rs(p)];
}
inline void push_up_min(ll p)//向上維護區間最小值
{
ans[p]=min(ans[ls(p)],ans[rs(p)]);
}
inline void push_up_max(ll p)//向上維護區間最大值
{
ans[p]=max(ans[ls(p)],ans[rs(p)]);
}
需要注意的是,這裡是從下往上維護父子節點的邏輯關係,因為當你一個子節點改變了之後,它所有的父親節點都需要改變。所以開始遞迴之後必然是先去整合子節點的資訊,再向它們的祖先反饋整合之後的資訊。
所以對於建樹,由於二叉樹的結構特點,我們可以選擇遞迴建樹,並且在遞迴的過程中要注意維護父子關係
void build(ll p,ll l,ll r)//p 根節點,l 區間左端點,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);
push_up(p);//記得維護父子關係
}
我們已經有了一棵線段樹,但是現在並不能對它進行什麼操作,它目前還並沒有什麼卵用
2.區間修改
假如我們要修改一個區間[l,r]內的值,我們發現由於二叉樹的結構特點,l和r很可能不在同一個節點上,這時候怎麼辦?
區間拆分了解一下
區間拆分是線段樹的核心操作,我們可以將一個區間[a, b]拆分成若干個節點,使得這些節點代表的區間加起來是[a, b],並且相互之間不重疊.
所有我們找到的這些節點就是”終止節點”.
區間拆分的步驟
從根節點[1, n]開始,考慮當前節點是[L, R].
如果[L, R]在[a, b]之內,那麼它就是一個終止節點.
否則,分別考慮[L, Mid],[Mid + 1, R]與[a, b]是否有交,遞迴兩邊繼續找終止節點
舉個栗子:
比如我們要從[1,10]中拆分出[2,8]這個區間
還是挺直觀的吧QwQ
這其實是一種分塊的思想
分塊的思想是通過將整個序列分為有窮個小塊,對於要查詢的一段區間,總是可以整合成k個所分塊與m個單個元素的資訊的並
所以我們應該充分利用區間拆分的性質,思考在終止節點要存什麼資訊,如何快速維護這些資訊,不要每次一變就到最底層
那麼對於區間操作,我們引入一個東西——懶標記(lazy tag)。那這個東西“lazy”在什麼地方呢?
我們發現原本的區間修改需要通過改變最下的葉子結點,然後不斷向上遞迴來修改祖先節點直至到達根節點,時間複雜度最多可以到O(n logn)的級別。
但是當我們用了懶標記後,時間複雜度就降低到了O(log n)的級別甚至更低
懶標記怎麼用?
如果整個區間都被操作,就把懶標記記錄在公共祖先節點上,如果只修改了一部分,就記錄在這部分的公共祖先上,如果只修改了自己的話,就只改變自己
然後,如果我們採用這種方式優化的話,我們需要在每一次區間查詢修改時pushdown一次,以免重複衝突
那麼怎麼傳導push down呢?
開始回溯是執行push up,因為是向上傳導資訊;如果我們想要他向下更新,就調整順序,在向下遞迴的時候push down
程式碼:
inline void f(ll p,ll l,ll r,ll k)
{
tag[p]=tag[p]+k;
ans[p]=ans[p]+k*(r-l+1);
//由於是這個區間統一改變,所以ans陣列要加元素個數
}
//f函式的唯一目的,就是記錄當前節點所代表的區間
inline void push_down(ll p,ll l,ll r)
{
ll mid=(l+r)>>1;
f(ls(p),l,mid,tag[p]);
f(rs(p),mid+1,r,tag[p]);
tag[p]=0;
//不斷向下遞迴更新子節點
}
inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
//nl,nr為要修改的區間
//l,r,p為當前節點所儲存的區間以及節點的編號
//k為要增加的值
if(nl<=l&&r<=nr)
{
ans[p]+=k*(r-l+1);
tag[p]+=k;
return;
}
push_down(p,l,r);
//回溯之前(也可以說是下一次遞迴之前,因為沒有遞迴就沒有回溯)
//由於是在回溯之前不斷向下傳遞,所以自然每個節點都可以更新到
ll mid=(l+r)>>1;
if(nl<=mid) update(nl,nr,l,mid,ls(p),k);
if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
push_up(p);
//回溯之後
}
對於複雜度而言,由於完全二叉樹的深度不超過logn,那麼單點修改顯然是O(logn)的,而區間修改的話,由於我們是分了log n個區間,每次查詢是O(1)的複雜度,所以複雜度是O(log n)的
3.區間查詢
這裡還是用到了分塊的思想,對於要查詢的區間做區間分解,然後遞迴求答案就好了
ll find(ll qx,ll qy,ll l,ll r,ll p)
{
//qx 區間左端點 qy區間右端點
//l當前左端點 r當前右端點
//p當前區間的編號
ll res=0;
if(qx<=l&&r<=qy) return ans[p];
ll mid=(l+r)>>1;
push_down(p,l,r);
if(qx<=mid) res+=find(qx,qy,l,mid,ls(p));
if(qy>mid) res+=find(qx,qy,mid+1,r,rs(p));
return res;
}
三.標程
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<time.h>
#include<queue>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pr;
const double pi=acos(-1);
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define Rep(i,u) for(int i=head[u];i;i=Next[i])
#define clr(a) memset(a,0,sizeof a)
#define pb push_back
#define mp make_pair
#define fi first
#define sc second
ld eps=1e-9;
ll pp=1000000007;
ll mo(ll a,ll pp){if(a>=0 && a<pp)return a;a%=pp;if(a<0)a+=pp;return a;}
ll powmod(ll a,ll b,ll pp){ll ans=1;for(;b;b>>=1,a=mo(a*a,pp))if(b&1)ans=mo(ans*a,pp);return ans;}
ll read(){
ll ans=0;
char last=' ',ch=getchar();
while(ch<'0' || ch>'9')last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans;
return ans;
}
//head
const int MAXN=1000001;
ll a[MAXN],ans[MAXN<<2],tag[MAXN<<2];
ll m,n;
inline ll ls(ll x)
{
return x<<1;
}
inline ll rs(ll x)
{
return (x<<1)+1;
}
inline void push_up(ll p)
{
ans[p]=ans[ls(p)]+ans[rs(p)];
}
inline void f(ll p,ll l,ll r,ll k)
{
tag[p]=tag[p]+k;
ans[p]=ans[p]+k*(r-l+1);
}
inline void push_down(ll p,ll l,ll r)
{
ll mid=(l+r)>>1;
f(ls(p),l,mid,tag[p]);
f(rs(p),mid+1,r,tag[p]);
tag[p]=0;
}
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);
push_up(p);
}
inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
if(nl<=l&&r<=nr)
{
ans[p]+=k*(r-l+1);
tag[p]+=k;
return;
}
push_down(p,l,r);
ll mid=(l+r)>>1;
if(nl<=mid) update(nl,nr,l,mid,ls(p),k);
if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
push_up(p);
}
ll find(ll qx,ll qy,ll l,ll r,ll p)
{
ll res=0;
if(qx<=l&&r<=qy) return ans[p];
ll mid=(l+r)>>1;
push_down(p,l,r);
if(qx<=mid) res+=find(qx,qy,l,mid,ls(p));
if(qy>mid) res+=find(qx,qy,mid+1,r,rs(p));
return res;
}
int main()
{
n=read(),m=read();
rep(i,1,n) a[i]=read();
build(1,1,n);
while(m--)
{
ll a1=read();
if(a1==1)
{
ll b=read(),c=read(),d=read();
update(b,c,1,n,1,d);
}
if(a1==2)
{
ll b=read(),c=read();
printf("%lld\n",find(b,c,1,n,1));
}
}
return 0;
}
特別鳴謝:_皎月半灑花 大佬題解
water_lift:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=100001;
ll sum[N<<2],lazy[N<<2];
ll a[N];
void build(ll cnt,ll l,ll r)
{
if(l==r)
{
sum[cnt]=a[l];
return;
}
else
{
ll mid=(l+r)>>1;
build(cnt<<1,l,mid);
build((cnt<<1)|1,mid+1,r);
sum[cnt]=sum[cnt<<1]+sum[(cnt<<1)+1];
}
}
inline bool cover(ll nl,ll nr,ll l,ll r)
{
return l<=nl&&r>=nr;
}
inline bool intersection(ll nl,ll nr,ll l,ll r)
{
return l<=nr&&r>=nl;
}
void pushdown(ll cnt,ll l,ll r)
{
ll mid=(l+r)>>1;
lazy[cnt<<1]+=lazy[cnt];
lazy[(cnt<<1)+1]+=lazy[cnt];
sum[cnt<<1]+=lazy[cnt]*(mid-l+1);
sum[(cnt<<1)+1]+=lazy[cnt]*(r-mid);
lazy[cnt]=0;
}
void add(ll cnt,ll nl,ll nr,ll l,ll r,ll a)
{
if(cover(nl,nr,l,r))
{
sum[cnt]+=(nr-nl+1)*a;
lazy[cnt]+=a;
return ;
}
pushdown(cnt,nl,nr);
ll mid=(nl+nr)>>1;
if(intersection(nl,mid,l,r)) add(cnt<<1,nl,mid,l,r,a);
if(intersection(mid+1,nr,l,r)) add(cnt<<1|1,mid+1,nr,l,r,a);
sum[cnt]=sum[cnt<<1]+sum[cnt<<1|1];
}
ll query(ll cnt,ll nl,ll nr,ll l,ll r)
{
if(cover(nl,nr,l,r))
{
return sum[cnt];
}
pushdown(cnt,nl,nr);
ll mid=(nl+nr)>>1;
ll ans=0;
if(intersection(nl,mid,l,r)) ans+=query(cnt*2,nl,mid,l,r);
if(intersection(mid+1,nr,l,r)) ans+=query(cnt*2+1,mid+1,nr,l,r);
return ans;
}
ll n,m;
int main()
{
cin>>n>>m;
for(ll i=1;i<=n;i++)
cin>>a[i];
build(1,1,n);
while(m--)
{
ll k;
scanf("%lld",&k);
if(k==1)
{
ll l,r,t;
scanf("%lld%lld%lld",&l,&r,&t);
add(1,1,n,l,r,t);
}
else if(k==2)
{
ll l,r;
scanf("%lld%lld",&l,&r);
printf("%lld\n",query(1,1,n,l,r));
}
}
}