1. 程式人生 > >數據結構-線段樹

數據結構-線段樹

lose inter 函數寫法 struct 部分 賦值 點子 查詢 增加

數據結構圖

eg:1-10的線段樹(區間裏面的數代表左右邊界值,區間下面的數代表在tree數組中的下標)

技術分享圖片

基本功能實現思路及代碼

0.基礎結構體

1 struct N
2 {
3     int l,r,w; //左邊界 右邊界 區間維護值 
4     int lazy; //懶值 
5 }tree[4*n];

註意:這裏的tree要開4倍n的大小,原因是開的區間中有一些是沒被利用的如上圖.(懶值的作用下面會說)

1.建樹-build函數

更新當前區間左右邊界+葉子節點處理(賦值)+往左右子節點擴展+更新

eg:區間維護的值為區間和(l,r,k初始值分別為1,n,0)(k為區間下標)

 1 void build(int l,int r,int k)
 2 {
 3     tree[k].l=l,tree[k].r=r;//更新當前區間左右邊界 
 4     if(l==r) //葉子節點處理 
 5     {
 6         scanf("%d",&tree[k].w);
 7         return ;
 8     }
 9     int mid=(l+r)>>1;
10     build(l,mid,k*2+1); //擴展左子區間 
11     build(mid+1,r,k*2+2); //擴展右子區間 
12
tree[k].w=tree[2*k+1].w+tree[2*k+2].w; //更新 13 }

2.單點查詢-ask_point函數

葉子區間處理(若到達葉子區間則找到值)+前進區間判斷(該詢問左子區間還是右子區間)

eg:區間維護的值為區間和(查詢第x個值)

1 int ask_point(int k,int x)
2 {
3     if(tree[k].l==tree[k].r) return tree[k].w; //葉子節點處理 
4     int mid=(tree[k].l+tree[k].r)>>1; //前進區間 
5     if
(x<=mid) return ask_point(k*2+1,x); 6 else return ask_point(k*2+2,x); 7 }

3.單點修改-add_point函數

單點查詢+更新

eg:區間維護的值為區間和(第x個值加上y)

 1 void add_point(int k,int x,int y)
 2 {
 3     if(tree[k].l==tree[k].r) //葉子區間處理 
 4     {
 5         tree[k].w+=y;
 6         return ;
 7     }
 8     int mid=(tree[k].l+tree[k].r)>>1; //前進區間 
 9     if(x<=mid) add_point(k*2+1,x,y);
10     else add_point(k*2+2,x,y);
11     tree[k].w=tree[k*2+1].w+tree[k*2+2].w; //更新 
12 }

4.區間查詢-ask_interval函數

被包含區間處理(若區間被包含則直接貢獻該區間的值)+前進區間判斷(需不需要收集左子區間和右子區間的值)

eg:區間維護的值為區間和(查詢(a,b)區間的區間和)

1 int ask_interval(int k,int a,int b)
2 {
3     if(tree[k].l>=a&&tree[k].r<=b) return tree[k].w;//區間被包含 
4     int mid=(tree[k].l+tree[k].r)>>1;
5     int sum=0; //用來收集左右子區間的值 
6     if(a<=mid) sum+=ask_interval(k*2+1,a,b);
7     if(b>mid) sum+=ask_interval(k*2+2,a,b);
8     return sum;
9 } 

5.區間修改-add_interval函數

這裏需要增加一個新概念-懶值

當一個節點區間要修改區間包含時,只需要改這個節點區間的值就行了,不需要往下修改子區間的值,當需要用到該區間的子區間時才去修改該區間的子區間的值.這個優化的實現就需要用到懶值,懶值存的是子區間需要修改的值的累積值,當需要用到子區間時就把懶值分配下去-down函數.

這裏給出down函數寫法(把懶值分配給子區間)

1 void down(int k)
2 {
3     tree[k*2+1].lazy+=tree[k].lazy;
4     tree[k*2+2].lazy+=tree[k].lazy;
5     tree[k*2+1].w+=tree[k].lazy*(tree[k*2+1].r-tree[k*2+1].l+1);
6     tree[k*2+2].w+=tree[k].lazy*(tree[k*2+2].r-tree[k*2+2].l+1);
7     tree[k].lazy=0;
8 }

add_interval函數寫法是在區間查詢的基礎上加上了down函數的利用和更新代碼

eg:區間維護的值為區間和(給(a,b)區間的值加上x)

 1 void add_interval(int k,int a,int b,int x)
 2 {
 3     if(tree[k].l>=a&&tree[k].r<=b)
 4     {
 5         tree[k].w+=x*(tree[k].r-tree[k].l+1);
 6         tree[k].lazy+=x;
 7         return ;
 8     }
 9     if(tree[k].lazy) down(k); //如果到這一步則表明需要用到該節點子區間的值 
10     int mid=(tree[k].l+tree[k].r)>>1;
11     if(a<=mid) add_interval(k*2+1,a,b,x);
12     if(b>mid) add_interval(k*2+2,a,b,x);
13     tree[k].w=tree[k*2+1].w+tree[k*2+2].w;
14 }

6.lazy引入的影響

前面所過當要用到用有lazy值的節點的子區間時,需要把lazy分配下去,所以引入lazy後除了build函數其他功能函數都必須追加一個判斷 if(lazy存在) down(k);

下面給出具有以上5個基本功能的線段樹代碼(區間維護的值為區間和)

技術分享圖片
  1 #include <cstdio>
  2 #include <cmath>
  3 #include <iostream>
  4 #include <cstring>
  5 #include <algorithm>
  6 #include <vector>
  7 #include <string>
  8 #include <utility>
  9 #include <queue>
 10 #include <stack>
 11 #include <map>
 12 #include <set> 
 13 const int INF=0x3f3f3f3f;
 14 using namespace std;
 15 
 16 const int MAX_N=100000; 
 17 struct N
 18 {
 19     int l,r,w;
 20     int lazy;
 21 }tree[4*MAX_N];
 22 
 23 void build(int l,int r,int k)
 24 {
 25     tree[k].l=l,tree[k].r=r;
 26     if(l==r)
 27     {
 28         scanf("%d",&tree[k].w);
 29         return ;
 30     }
 31     int mid=(l+r)>>1;
 32     build(l,mid,k*2+1); 
 33     build(mid+1,r,k*2+2); 
 34     tree[k].w=tree[2*k+1].w+tree[2*k+2].w; 
 35 }
 36 
 37 void down(int k)
 38 {
 39     tree[k*2+1].lazy+=tree[k].lazy;
 40     tree[k*2+2].lazy+=tree[k].lazy;
 41     tree[k*2+1].w+=tree[k].lazy*(tree[k*2+1].r-tree[k*2+1].l+1);
 42     tree[k*2+2].w+=tree[k].lazy*(tree[k*2+2].r-tree[k*2+2].l+1);
 43     tree[k].lazy=0;
 44 }
 45 
 46 int ask_point(int k,int x)
 47 {
 48     if(tree[k].l==tree[k].r) return tree[k].w; 
 49     if(tree[k].lazy) down(k); //只添加了該判斷 
 50     int mid=(tree[k].l+tree[k].r)>>1; 
 51     if(x<=mid) return ask_point(k*2+1,x);
 52     else return ask_point(k*2+2,x);
 53 }
 54 
 55 void add_point(int k,int x,int y)
 56 {
 57     if(tree[k].l==tree[k].r) 
 58     {
 59         tree[k].w+=y;
 60         return ;
 61     }
 62     if(tree[k].lazy) down(k); //只添加了該判斷  
 63     int mid=(tree[k].l+tree[k].r)>>1; 
 64     if(x<=mid) add_point(k*2+1,x,y);
 65     else add_point(k*2+2,x,y);
 66     tree[k].w=tree[k*2+1].w+tree[k*2+2].w; 
 67 }
 68 
 69 int ask_interval(int k,int a,int b)
 70 {
 71     if(tree[k].l>=a&&tree[k].r<=b) return tree[k].w;
 72     if(tree[k].lazy) down(k); //只添加了該判斷 
 73     int mid=(tree[k].l+tree[k].r)>>1;
 74     int sum=0; 
 75     if(a<=mid) sum+=ask_interval(k*2+1,a,b);
 76     if(b>mid) sum+=ask_interval(k*2+2,a,b);
 77     return sum;
 78 } 
 79 
 80 void add_interval(int k,int a,int b,int x)
 81 {
 82     if(tree[k].l>=a&&tree[k].r<=b)
 83     {
 84         tree[k].w+=x*(tree[k].r-tree[k].l+1);
 85         tree[k].lazy+=x;
 86         return ;
 87     }
 88     if(tree[k].lazy) down(k);  
 89     int mid=(tree[k].l+tree[k].r)>>1;
 90     if(a<=mid) add_interval(k*2+1,a,b,x);
 91     if(b>mid) add_interval(k*2+2,a,b,x);
 92     tree[k].w=tree[k*2+1].w+tree[k*2+2].w;
 93 }
 94 
 95 int main()
 96 {
 97     int n;
 98     cin>>n;
 99     build(1,n,0);
100     //該部分隨題意添加 
101     return 0;
102 }
View Code

數據結構-線段樹