線段樹 模板題
線段樹
Problem Description
已知一個數列,你需要進行下面兩種操作:
1.將某區間每一個數加上x
2.求出某區間每一個數的和
Input
第一行包含兩個整數N、M,分別表示該數列數字的個數和操作的總個數。
第二行包含N個用空格分隔的整數,其中第i個數字表示數列第i項的初始值。
接下來M行每行包含3或4個整數,表示一個操作,具體如下:
操作1: 格式:1 x y k 含義:將區間[x,y]內每個數加上k
操作2: 格式:2 x y 含義:輸出區間[x,y]內每個數的和
Output
包含若干行整數,即為所有操作2的結果。
Sample Input
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
Sample Output
11
8
20
Tip
時空限制:1000ms,128M
Data Constraint
對於30%的資料:N≤8,M≤10
對於70%的資料:N≤1000,M≤10000
對於100%的資料:N≤100000,M≤100000
剛剛看到本題,很顯然,我們可以先嚐試一下暴力,當然,如果分塊的話,我們也可以大大加快我們對本題的處理。
那麼我們就有非常簡單的暴力程式碼。
#include <cstdio> #define LL long long using namespace std; LL n,m; LL a[1000001]; int main() { scanf("%lld%lld",&n,&m); for (LL i=1;i<=n;i++) scanf("%lld",&a[i]); for (LL i=1;i<=m;i++) { LL k,x,y,j; scanf("%lld%lld%lld",&j,&x,&y); switch (j){ case 1: scanf("%lld",&k); for (LL p=x;p<=y;p++) a[p]+=k; break; case 2: { LL ans=0; for (LL p=x;p<=y;p++) ans+=a[p]; printf("%lld\n",ans); } break; } } }
觀察本題,很顯然我們需要建立一棵線段樹,其每個節點都儲存一段區間的和, 並且我們可能需要一個布林函式來幫助我們快速修改一段區間的值。當然,如果題目只是一個值一個值修改的話,我們可以選擇樹狀陣列來更快地處理問題。
那麼,什麼是線段樹呢?
線段樹是一種二叉搜尋樹,與區間樹相似,它將一個區間劃分成一些單元區間,每個單元區間對應線段樹中的一個葉結點。
簡單來說,線段樹的建立,一般是將一個數組tree[],每一個編號i都算一個節點,那麼我們就可以得到任意一個線段樹的節點 tree[i],並且使用結構體的方式儲存。
這樣,我們的tree[i],就可以記錄下它儲存的區間和的左端點 left 和右端點 right,以及這段區間的和 sum。同時,線段樹之所以是線段樹,是因為它的祖先、兒子存在包含關係。
簡單來說,如果我已經有一個節點tree[i],那麼我們通過它所儲存的區間的中點 mid=(left+right)/2 ,那麼我可以再為這個節點 tree[i] 記錄兩個值 leftson , rightson ,表示它的左兒子和右兒子,那麼,左右兒子分別繼承父親節點的一部分割槽間和,為了儘量降低複雜度,我們選擇左右兒子分別繼承父親節點的一半區間,所得到的左兒子 tree[leftson] ,它的區間左端點仍為 left , 右端點變成了mid 。所以右兒子 tree[rightson],它的區間左端點則為 mid+1 ,右端點則是 right 。由此我們明白,左右兒子所記錄的區間和就是父親節點記錄的區間和,那麼我們就可以通過這一點,進行一個搜尋。
我們從根節點進行搜尋一段區間,判斷這段區間是繼承到了左兒子還是右兒子處,也可能是兩個兒子分別佔有一部分,然後分別進行搜尋,就可以得到答案。
也正是因為每個節點記錄的是一段區間和,所以我們修改一段區間的數的大小時,我們只需一個節點一個節點處理就可以了,當然,為了節約時間,我們可以引入變數 change 來記錄這個節點的所有兒子和它是否需要修改,然後在我們遍歷到存在change 的點時,我們只需要增加該節點的和,並將change繼承給它的各個兒子,就可以了。
所以,我們有如下程式碼。
#include <cstdio>
#define LL long long
using namespace std;
LL n,m;
LL q[100010];
struct node{
LL left,right,sum,change;
}v[270000];
void build(LL ,LL ,LL );
void change(LL ,LL ,LL ,LL );
LL find(LL ,LL ,LL );
int main()
{
scanf("%lld%lld",&n,&m);
for (LL i=1;i<=n;i++)
scanf("%lld",&q[i]);
build(1,1,n);
for (LL i=1;i<=m;i++)
{
LL j,x,y,k;
scanf("%lld%lld%lld",&j,&x,&y);
if (j==1)
{
scanf("%lld",&k);
change(1,x,y,k);
}
else
printf("%lld\n",find(1,x,y));
}
return 0;
}
LL find(LL ro,LL le,LL ri)
{
if (v[ro].change)
{
v[ro].sum+=v[ro].change*(v[ro].right-v[ro].left+1);
if (v[ro].left<v[ro].right)
{
v[ro<<1].change+=v[ro].change;
v[ro<<1|1].change+=v[ro].change;
}
v[ro].change=0;
}
if (le==v[ro].left&&ri==v[ro].right)
return v[ro].sum;
LL mid=(v[ro].left+v[ro].right)>>1;
if (ri<=mid)
return find(ro<<1,le,ri);
if (le>mid)
return find(ro<<1|1,le,ri);
return find(ro<<1,le,mid)+find(ro<<1|1,mid+1,ri);
}
void change(LL ro,LL le,LL ri,LL ca)
{
v[ro].sum+=(ri-le+1)*ca;
if (le==v[ro].left&&ri==v[ro].right)
{
if (v[ro].left<v[ro].right)
{
v[ro<<1].change+=ca;
v[ro<<1|1].change+=ca;
}
return ;
}
if (v[ro].change)
{
v[ro].sum+=v[ro].change*(v[ro].right-v[ro].left+1);
v[ro<<1].change+=v[ro].change;
v[ro<<1|1].change+=v[ro].change;
v[ro].change=0;
}
LL mid=(v[ro].left+v[ro].right)>>1;
if (ri<=mid)
{
change(ro<<1,le,ri,ca);
return ;
}
if (le>mid)
{
change(ro<<1|1,le,ri,ca);
return ;
}
change(ro<<1,le,mid,ca);
change(ro<<1|1,mid+1,ri,ca);
return ;
}
void build(LL ro,LL le,LL ri)
{
v[ro].left=le;v[ro].right=ri;
if (le<ri)
{
LL mid=(le+ri)>>1;
build(ro<<1,le,mid);
build(ro<<1|1,mid+1,ri);
v[ro].sum=v[ro<<1].sum+v[ro<<1|1].sum;
}
else
v[ro].sum=q[le];
return ;
}