線段樹(一種二叉搜索樹)
樹形結構的特點讓它更方便查詢搜索。
線段樹方便與對區間查詢,區間更新維護,這是因爲樹上的父親節點表示了一段區間。葉子結點纔是原始的元素。
如上圖所示:
利用線段樹解決問題的步驟如下(以模版題——給N個數,求任意區間合爲例子):
對於這個算法個人認爲必需理解:1.在建樹時怎樣將樹全部遍歷,將節點處理成區間和。2.在維護區間時利用lazy標記將數據添加到樹中,並且如何在需要時將節點的lazy標記壓入下面的節點。3.在查詢區間時如何在需要時將節點的lazy標記壓入下面的節點。
以上所述是算法的精髓所在,通則達。
詳解如下
//預處理數據和主函數,方便後續閱讀代碼。p[]數組存放錄入的數據,tr[N<<2]存放節點的值和lazy標記。
const int N=1e5+5;
int p[N];
struct node{
long long sum,lazy;
}tr[N<<2];
int main()
{
int n,m,i,k,a,b,q;
scanf("%d%d",&n,&m); //輸入n個數字,m次處理
for(i=1;i<=n;i++){
scanf("%d",&p[i]);
}
build_tree(1,1,n);
while(m--){
scanf("%d",&q);
if(q==1){ //1表示區間加上k值
scanf("%d%d%d",&a,&b,&k);
add(1,1,n,a,b,k);
}
else{ //2表示 查詢a到b的和
scanf("%d%d",&a,&b);
printf("%lld\n",search_tree(1,1,n,a,b));
}
}
return 0;
}
1.利用題目中的關係建樹,利用DFS,遞歸遍歷到樹的每一個點,這裏面主要涉及到遞歸的回溯,將所有點都遍歷到;
代碼如下:
void update(int root)
{
tr[root].sum=tr[root<<1].sum+tr[root<<1|1].sum; //將左右兒子節點上加到父親節點。這就是區間的含義。
}
void build_tree(int root, int l, int r)
{
if(l==r){
tr[root].sum=p[l] ; //滿足條件(遍歷到了葉子結點)
return;
}
int mid=(l+r)>>1;
build_tree(root<<1,l,mid); //分流,左兒子;
build_tree(root<<1|1,mid+1,r); //分流,右兒子;
update(root); //傳遞給父親節點
}
要是沒學過dfs 理解會比較但一張圖就可以讓你豁然開朗!
2.下面是對區間的維護,對區間的維護就赤裸裸地體現了線段樹的優勢所在,每次查詢一次就只需要查詢到滿足左右區間的父親節點位置即可 ,有了lazy標記這玩意兒就大大縮減了所需時間。
代碼如下:
void pushdown(int root, int l, int r) //pushdown函數使原節點的lazy值壓入左右兒子
{
if(!tr[root].lazy) return; //一種剪枝吧,不錯。
int mid=(l+r)>>1;
tr[root<<1].sum+=(tr[root].lazy*(mid-l+1)); //更新左兒子節點的和
tr[root<<1|1].sum+=(tr[root].lazy*(r-mid)); //更新右兒子節點的和
tr[root<<1].lazy+=tr[root].lazy; //更新左兒子節點lazy標記
tr[root<<1|1].lazy+=tr[root].lazy; //更新右兒子節點lazy標記
tr[root].lazy=0; //將父親節點的lazy標記清零
}
void add(int root, int l, int r, int a, int b, int k)
{
if(l==a&&r==b){ //找到了目標節點區域
tr[root].lazy+=k; //更新
tr[root].sum+=k*(l-r+1); //更新
return ;
}
pushdown(root,l,r); // 每一次都要檢查是否需要將父親節點壓入左右節點。
int mid=(l+r)>>1;
if(mid>=b){ // 需要到左區間
add(root<<1,l,mid,a,b,k);
}
else if(mid<a){ // 需要到右區間
add(root<<1|1,mid+1,r,a,b,k);
}
else{ // 左右區間都需要去找;
add(root<<1,l,mid,a,mid,k);
add(root<<1|1,mid+1,r,mid+1,b,k);
}
update(root); //同樣需要將父親節點更新;
}
3.查詢區間和;(這個點和上面的區間維護相似,主要也是區間查詢,不同之處就是要返回區間的值)
代碼如下:
long long search_tree(int root, int l, int r, int a, int b)
{
if(l==a&&r==b){
return tr[root].sum;
}
pushdown(root,l,r); // 每次檢查是否需要將父親節點壓入子節點;
int mid=(l+r)>>1;
if(mid>=b){
return search_tree(root<<1,l,mid,a,b); //返回查到的值,同樣是遞歸實現;
}
else if(mid<a){
return search_tree(root<<1|1,mid+1,r,a,b);
}
else{
return search_tree(root<<1,l,mid,a,mid)+search_tree(root<<1|1,mid+1,r,mid+1,b);
}
}
學習這個算法時主要是要將遞歸實現理解透徹,將lazy標記這一方法理解透徹,將線段樹這三個字理解透徹,區間區間區間?????No can no bibi !! …<~ - ~>…
耐心出奇跡
代碼太長建議分塊理解爲妙:
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+5;
int p[N];
struct node{
long long sum,lazy;
}tr[N<<2];
void update(int root)
{
tr[root].sum=tr[root<<1].sum+tr[root<<1|1].sum;
}
void build_tree(int root, int l, int r)
{
if(l==r){
tr[root].sum=p[l];
return;
}
int mid=(l+r)>>1;
build_tree(root<<1,l,mid);
build_tree(root<<1|1,mid+1,r);
update(root);
}
void pushdown(int root, int l, int r)
{
if(!tr[root].lazy) return;
int mid=(l+r)>>1;
tr[root<<1].sum+=(tr[root].lazy*(mid-l+1));
tr[root<<1|1].sum+=(tr[root].lazy*(r-mid));
tr[root<<1].lazy+=tr[root].lazy;
tr[root<<1|1].lazy+=tr[root].lazy;
tr[root].lazy=0;
}
void add(int root, int l, int r, int a, int b, int k)
{
if(l==a&&r==b){
tr[root].lazy+=k;
tr[root].sum+=k*(l-r+1);
return ;
}
pushdown(root,l,r);
int mid=(l+r)>>1;
if(mid>=b){
add(root<<1,l,mid,a,b,k);
}
else if(mid<a){
add(root<<1|1,mid+1,r,a,b,k);
}
else{
add(root<<1,l,mid,a,mid,k);
add(root<<1|1,mid+1,r,mid+1,b,k);
}
update(root);
}
long long search_tree(int root, int l, int r, int a, int b)
{
if(l==a&&r==b){
return tr[root].sum;
}
pushdown(root,l,r);
int mid=(l+r)>>1;
if(mid>=b){
return search_tree(root<<1,l,mid,a,b);
}
else if(mid<a){
return search_tree(root<<1|1,mid+1,r,a,b);
}
else{
return search_tree(root<<1,l,mid,a,mid)+search_tree(root<<1|1,mid+1,r,mid+1,b);
}
}
int main()
{
int n,m,i,k,a,b,q;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%d",&p[i]);
}
build_tree(1,1,n);
while(m--){
scanf("%d",&q);
if(q==1){
scanf("%d%d%d",&a,&b,&k);
add(1,1,n,a,b,k);
}
else{
scanf("%d%d",&a,&b);
printf("%lld\n",search_tree(1,1,n,a,b));
}
}
return 0;
}
謝謝觀看,喜歡就收藏唄!