线段树



 

数据结构专题——线段树

标签: buildquery存储c
  61010人阅读  评论(40)  收藏  举报
本文章已收录于: 
 C语言知识库
  分类:
ACM回忆(46) 

目录(?)[+]

线段树


转载请注明出处,谢谢!http://blog.csdn.net/metalseed/article/details/8039326 

持续更新中···


一:线段树基本概念

1:概述

线段树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(lgN)!

性质:父亲的区间是[a,b],(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b],线段树需要的空间为数组大小的四倍


2:基本操作(demo用的是查询区间最小值)

线段树的主要操作有:

(1):线段树的构造 void build(int node, int begin, int end);

主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,最后回溯的时候给当前节点赋值

[cpp]  view plain  copy
 print ?
  1. #include   
  2. using namespace std;  
  3.   
  4. const int maxind = 256;  
  5. int segTree[maxind * 4 + 10];  
  6. int array[maxind];   
  7. /* 构造函数,得到线段树 */  
  8. void build(int node, int begin, int end)    
  9. {    
  10.     if (begin == end)    
  11.         segTree[node] = array[begin]; /* 只有一个元素,节点记录该单元素 */  
  12.     else    
  13.     {     
  14.         /* 递归构造左右子树 */   
  15.         build(2*node, begin, (begin+end)/2);    
  16.         build(2*node+1, (begin+end)/2+1, end);   
  17.            
  18.         /* 回溯时得到当前node节点的线段信息 */    
  19.         if (segTree[2 * node] <= segTree[2 * node + 1])    
  20.             segTree[node] = segTree[2 * node];    
  21.         else    
  22.             segTree[node] = segTree[2 * node + 1];    
  23.     }    
  24. }  
  25.   
  26. int main()  
  27. {  
  28.     array[0] = 1, array[1] = 2,array[2] = 2, array[3] = 4, array[4] = 1, array[5] = 3;  
  29.     build(1, 0, 5);  
  30.     for(int i = 1; i<=20; ++i)  
  31.      cout<< "seg"<< i << "=" <
  32.     return 0;  
  33. }   
 此build构造成的树如图:

(2):区间查询int query(int node, int begin, int end, int left, int right);

(其中node为当前查询节点,begin,end为当前节点存储的区间,left,right为此次query所要查询的区间)

主要思想是把所要查询的区间[a,b]划分为线段树上的节点,然后将这些节点代表的区间合并起来得到所需信息

比如前面一个图中所示的树,如果询问区间是[0,2],或者询问的区间是[3,3],不难直接找到对应的节点回答这一问题。但并不是所有的提问都这么容易回答,比如[0,3],就没有哪一个节点记录了这个区间的最小值。当然,解决方法也不难找到:把[0,2][3,3]两个区间(它们在整数意义上是相连的两个区间)的最小值合并起来,也就是求这两个最小值的最小值,就能求出[0,3]范围的最小值。同理,对于其他询问的区间,也都可以找到若干个相连的区间,合并后可以得到询问的区间。

[cpp]  view plain  copy
 print ?
  1. int query(int node, int begin, int end, int left, int right)    
  2. {   
  3.     int p1, p2;    
  4.     
  5.     /*  查询区间和要求的区间没有交集  */  
  6.     if (left > end || right < begin)    
  7.         return -1;    
  8.     
  9.     /*  if the current interval is included in  */    
  10.     /*  the query interval return segTree[node]  */  
  11.     if (begin >= left && end <= right)    
  12.         return segTree[node];    
  13.     
  14.     /*  compute the minimum position in the  */  
  15.     /*  left and right part of the interval  */   
  16.     p1 = query(2 * node, begin, (begin + end) / 2, left, right);   
  17.     p2 = query(2 * node + 1, (begin + end) / 2 + 1, end, left, right);    
  18.     
  19.     /*  return the expect value  */   
  20.     if (p1 == -1)    
  21.         return p2;    
  22.     if (p2 == -1)    
  23.         return p1;    
  24.     if (p1 <= p2)    
  25.         return  p1;    
  26.     return  p2;      
  27. }   

可见,这样的过程一定选出了尽量少的区间,它们相连后正好涵盖了整个[left,right],没有重复也没有遗漏。同时,考虑到线段树上每层的节点最多会被选取2个,一共选取的节点数也是O(log n)的,因此查询的时间复杂度也是O(log n)。

线段树并不适合所有区间查询情况,它的使用条件是“相邻的区间的信息可以被合并成两个区间的并区间的信息”。即问题是可以被分解解决的。



(3):区间或节点的更新 及 线段树的动态维护update (这是线段树核心价值所在,节点中的标记域可以解决N多种问题)

动态维护需要用到标记域,延迟标记等。

a:单节点更新

[cpp]  view plain  copy
 print ?
  1. void Updata(int node, int begin, int end, int ind, int add)/*单节点更新*/    
  2. {    
  3.     
  4.     if( begin == end )    
  5.     {    
  6.         segTree[node] += add;    
  7.         return ;    
  8.     }    
  9.     int m = ( left + right ) >> 1;    
  10.     if(ind <= m)    
  11.         Updata(node * 2,left, m, ind, add);    
  12.     else    
  13.         Updata(node * 2 + 1, m + 1, right, ind, add);    
  14.     /*回溯更新父节点*/    
  15.     segTree[node] = min(segTree[node * 2], segTree[node * 2 + 1]);     
  16.          
  17. }   

b:区间更新(线段树中最有用的)

需要用到延迟标记,每个结点新增加一个标记,记录这个结点是否被进行了某种修改操作(这种修改操作会影响其子结点)。对于任意区间的修改,我们先按照查询的方式将其划分成线段树中的结点,然后修改这些结点的信息,并给这些结点标上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个结点p,并且决定考虑其子结点,那么我们就要看看结点p有没有标记,如果有,就要按照标记修改其子结点的信息,并且给子结点都标上相同的标记,同时消掉p的标记。(优点在于,不用将区间内的所有值都暴力更新,大大提高效率,因此区间更新是最优用的操作)

void Change来自dongxicheng.org

[cpp]  view plain  copy
 print ?
  1. void Change(node *p, int a, int b) /* 当前考察结点为p,修改区间为(a,b]*/  
  2.    
  3. {  
  4.    
  5.   if (a <= p->Left && p->Right <= b)  
  6.    
  7.   /* 如果当前结点的区间包含在修改区间内*/  
  8.    
  9.   {  
  10.    
  11.      ...... /* 修改当前结点的信息,并标上标记*/  
  12.    
  13.      return;  
  14.    
  15.   }  
  16.    
  17.   Push_Down(p); /* 把当前结点的标记向下传递*/  
  18.    
  19.   int mid = (p->Left + p->Right) / 2; /* 计算左右子结点的分隔点 
  20.   
  21.   if (a < mid) Change(p->Lch, a, b); /* 和左孩子有交集,考察左子结点*/  
  22.    
  23.   if (b > mid) Change(p->Rch, a, b); /* 和右孩子有交集,考察右子结点*/  
  24.    
  25.   Update(p); /* 维护当前结点的信息(因为其子结点的信息可能有更改)*/  
  26.    
  27. }  



3:主要应用

(1):区间最值查询问题 (见模板1)

(2):连续区间修改或者单节点更新的动态查询问题 (见模板2)

(3):多维空间的动态查询 (见模板3)


二:典型模板

模板1:

RMQ,查询区间最值下标---min

[cpp]  view plain  copy
 print ?
  1. #include    
  2.   
  3. using namespace std;    
  4.     
  5. #define MAXN 100    
  6. #define MAXIND 256 //线段树节点个数    
  7.     
  8. //构建线段树,目的:得到M数组.    
  9. void build(int node, int b, int e, int M[], int A[])    
  10. {    
  11.     if (b == e)    
  12.         M[node] = b; //只有一个元素,只有一个下标    
  13.     else    
  14.     {     
  15.         build(2 * node, b, (b + e) / 2, M, A);    
  16.         build(2 * node + 1, (b + e) / 2 + 1, e, M, A);    
  17.   
  18.         if (A[M[2 * node]] <= A[M[2 * node + 1]])    
  19.             M[node] = M[2 * node];    
  20.         else    
  21.             M[node] = M[2 * node + 1];    
  22.     }    
  23. }    
  24.     
  25. //找出区间 [i, j] 上的最小值的索引    
  26. int query(int node, int b, int e, int M[], int A[], int i, int j)    
  27. {    
  28.     int p1, p2;    
  29.     
  30.     //查询区间和要求的区间没有交集    
  31.     if (i > e || j < b)    
  32.         return -1;    
  33.   
  34.     if (b >= i && e <= j)    
  35.         return M[node];    
  36.    
  37.     p1 = query(2 * node, b, (b + e) / 2, M, A, i, j);    
  38.     p2 = query(2 * node + 1, (b + e) / 2 + 1, e, M, A, i, j);    
  39.     
  40.     //return the position where the overall    
  41.     //minimum is    
  42.     if (p1 == -1)    
  43.         return M[node] = p2;    
  44.     if (p2 == -1)    
  45.         return M[node] = p1;    
  46.     if (A[p1] <= A[p2])    
  47.         return M[node] = p1;    
  48.     return M[node] = p2;    
  49.     
  50. }    
  51.     
  52.     
  53. int main()    
  54. {    
  55.     int M[MAXIND]; //下标1起才有意义,否则不是二叉树,保存下标编号节点对应区间最小值的下标.    
  56.     memset(M,-1,sizeof(M));    
  57.     int a[]={3,4,5,7,2,1,0,3,4,5};    
  58.     build(1, 0, sizeof(a)/sizeof(a[0])-1, M, a);    
  59.     cout<sizeof(a)/sizeof(a[0])-1, M, a, 0, 5)<
  60.     return 0;    
  61. }    



模板2:

连续区间修改或者单节点更新的动态查询问题 (此模板查询区间和)

[cpp]  view plain  copy
 print ?
  1. #include     
  2. #include     
  3. using namespace std;    
  4.      
  5. #define lson l , m , rt << 1    
  6. #define rson m + 1 , r , rt << 1 | 1   
  7. #define root 1 , N , 1   
  8. #define LL long long    
  9. const int maxn = 111111;    
  10. LL add[maxn<<2];    
  11. LL sum[maxn<<2];    
  12. void PushUp(int rt) {    
  13.     sum[rt] = sum[rt<<1] + sum[rt<<1|1];    
  14. }    
  15. void PushDown(int rt,int m) {    
  16.     if (add[rt]) {    
  17.         add[rt<<1] += add[rt];    
  18.         add[rt<<1|1] += add[rt];    
  19.         sum[rt<<1] += add[rt] * (m - (m >> 1));    
  20.         sum[rt<<1|1] += add[rt] * (m >> 1);    
  21.         add[rt] = 0;    
  22.     }    
  23. }    
  24. void build(int l,int r,int rt) {    
  25.     add[rt] = 0;    
  26.     if (l == r) {    
  27.         scanf("%lld",&sum[rt]);    
  28.         return ;    
  29.     }    
  30.     int m = (l + r) >> 1;    
  31.     build(lson);    
  32.     build(rson);    
  33.     PushUp(rt);    
  34. }    
  35. void update(int L,int R,int c,int l,int r,int rt) {    
  36.     if (L <= l && r <= R) {    
  37.         add[rt] += c;    
  38.         sum[rt] += (LL)c * (r - l + 1);    
  39.         return ;    
  40.     }    
  41.     PushDown(rt , r - l + 1);    
  42.     int m = (l + r) >> 1;    
  43.     if (L <= m) update(L , R , c , lson);    
  44.     if (m < R) update(L , R , c , rson);    
  45.     PushUp(rt);    
  46. }    
  47. LL query(int L,int R,int l,int r,int rt) {    
  48.     if (L <= l && r <= R) {    
  49.         return sum[rt];    
  50.     }    
  51.     PushDown(rt , r - l + 1);    
  52.     int m = (l + r) >> 1;    
  53.     LL ret = 0;    
  54.     if (L <= m) ret += query(L , R , lson);    
  55.     if (m < R) ret += query(L , R , rson);    
  56.     return ret;    
  57. }    
  58. int main() {    
  59.     int N , Q;    
  60.     scanf("%d%d",&N,&Q);    
  61.     build(root);    
  62.     while (Q --) {    
  63.         char op[2];    
  64.         int a , b , c;    
  65.         scanf("%s",op);    
  66.         if (op[0] == 'Q') {    
  67.             scanf("%d%d",&a,&b);    
  68.             printf("%lld\n",query(a , b ,root));    
  69.         } else {    
  70.             scanf("%d%d%d",&a,&b,&c);    
  71.             update(a , b , c , root);    
  72.         }    
  73.     }    
  74.     return 0;    
  75. }    


模板3:

多维空间的动态查询



三:练习题目

下面是hh线段树代码,典型练习哇~

在代码前先介绍一些我的线段树风格:

  • maxn是题目给的最大区间,而节点数要开4倍,确切的来说节点数要开大于maxn的最小2x的两倍
  • lson和rson分辨表示结点的左儿子和右儿子,由于每次传参数的时候都固定是这几个变量,所以可以用预定于比较方便的表示
  • 以前的写法是另外开两个个数组记录每个结点所表示的区间,其实这个区间不必保存,一边算一边传下去就行,只需要写函数的时候多两个参数,结合lson和rson的预定义可以很方便
  • PushUP(int rt)是把当前结点的信息更新到父结点
  • PushDown(int rt)是把当前结点的信息更新给儿子结点
  • rt表示当前子树的根(root),也就是当前所在的结点

整理这些题目后我觉得线段树的题目整体上可以分成以下四个部分:



单点更新:最最基础的线段树,只更新叶子节点,然后把信息用PushUP(int r)这个函数更新上来


  • hdu1166 敌兵布阵
  • 题意:O(-1)
  • 思路:O(-1)
    线段树功能:update:单点增减 query:区间求和

code:

[cpp]  view plain  copy
 print ?
  1. #include  
  2. #include  
  3.   
  4. #define M 50005  
  5. #define lson l,m,rt<<1  
  6. #define rson m+1,r,rt<<1|1  
  7. /*left,right,root,middle*/  
  8.   
  9. int sum[M<<2];  
  10.   
  11. inline void PushPlus(int rt)  
  12. {  
  13.     sum[rt] = sum[rt<<1] + sum[rt<<1|1];  
  14. }  
  15.   
  16. void Build(int l, int r, int rt)  
  17. {  
  18.     if(l == r)  
  19.     {  
  20.         scanf("%d", &sum[rt]);  
  21.         return ;  
  22.     }  
  23.     int m = ( l + r )>>1;  
  24.   
  25.     Build(lson);  
  26.     Build(rson);  
  27.     PushPlus(rt);  
  28. }  
  29.   
  30. void Updata(int p, int add, int l, int r, int rt)  
  31. {  
  32.   
  33.     if( l == r )  
  34.     {  
  35.         sum[rt] += add;  
  36.         return ;  
  37.     }  
  38.     int m = ( l + r ) >> 1;  
  39.     if(p <= m)  
  40.         Updata(p, add, lson);  
  41.     else  
  42.         Updata(p, add, rson);  
  43.   
  44.     PushPlus(rt);  
  45. }  
  46.   
  47. int Query(int L,int R,int l,int r,int rt)  
  48. {  
  49.     if( L <= l && r <= R )  
  50.     {  
  51.         return sum[rt];  
  52.     }  
  53.     int m = ( l + r ) >> 1;  
  54.     int ans=0;  
  55.     if(L<=m )  
  56.         ans+=Query(L,R,lson);  
  57.     if(R>m)  
  58.         ans+=Query(L,R,rson);  
  59.   
  60.     return ans;  
  61. }  
  62. int main()  
  63. {     
  64.     int T, n, a, b;  
  65.     scanf("%d",&T);  
  66.     forint i = 1; i <= T; ++i )  
  67.     {  
  68.         printf("Case %d:\n",i);  
  69.         scanf("%d",&n);  
  70.         Build(1,n,1);  
  71.   
  72.         char op[10];  
  73.   
  74.         while( scanf("%s",op) &&op[0]!='E' )  
  75.         {  
  76.   
  77.             scanf("%d %d", &a, &b);  
  78.             if(op[0] == 'Q')  
  79.                 printf("%d\n",Query(a,b,1,n,1));  
  80.             else if(op[0] == 'S')  
  81.                 Updata(a,-b,1,n,1);  
  82.             else  
  83.                 Updata(a,b,1,n,1);  
  84.   
  85.         }  
  86.     }  
  87.     return 0;  
  88. }  

hdu1754 I Hate It
题意:O(-1)
思路:O(-1)
线段树功能:update:单点替换 query:区间最值

[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. using namespace std;  
  4.    
  5. #define lson l , m , rt << 1  
  6. #define rson m + 1 , r , rt << 1 | 1  
  7. const int maxn = 222222;  
  8. int MAX[maxn<<2];  
  9. void PushUP(int rt) {  
  10.     MAX[rt] = max(MAX[rt<<1] , MAX[rt<<1|1]);  
  11. }  
  12. void build(int l,int r,int rt) {  
  13.     if (l == r) {  
  14.         scanf("%d",&MAX[rt]);  
  15.         return ;  
  16.     }  
  17.     int m = (l + r) >> 1;  
  18.     build(lson);  
  19.     build(rson);  
  20.     PushUP(rt);  
  21. }  
  22. void update(int p,int sc,int l,int r,int rt) {  
  23.     if (l == r) {  
  24.         MAX[rt] = sc;  
  25.         return ;  
  26.     }  
  27.     int m = (l + r) >> 1;  
  28.     if (p <= m) update(p , sc , lson);  
  29.     else update(p , sc , rson);  
  30.     PushUP(rt);  
  31. }  
  32. int query(int L,int R,int l,int r,int rt) {  
  33.     if (L <= l && r <= R) {  
  34.         return MAX[rt];  
  35.     }  
  36.     int m = (l + r) >> 1;  
  37.     int ret = 0;  
  38.     if (L <= m) ret = max(ret , query(L , R , lson));  
  39.     if (R > m) ret = max(ret , query(L , R , rson));  
  40.     return ret;  
  41. }  
  42. int main() {  
  43.     int n , m;  
  44.     while (~scanf("%d%d",&n,&m)) {  
  45.         build(1 , n , 1);  
  46.         while (m --) {  
  47.             char op[2];  
  48.             int a , b;  
  49.             scanf("%s%d%d",op,&a,&b);  
  50.             if (op[0] == 'Q') printf("%d\n",query(a , b , 1 , n , 1));  
  51.             else update(a , b , 1 , n , 1);  
  52.         }  
  53.     }  
  54.     return 0;  
  55. }  

hdu1394 Minimum Inversion Number
题意:求Inversion后的最小逆序数
思路:用O(nlogn)复杂度求出最初逆序数后,就可以用O(1)的复杂度分别递推出其他解
线段树功能:update:单点增减 query:区间求和

[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. using namespace std;  
  4.    
  5. #define lson l , m , rt << 1  
  6. #define rson m + 1 , r , rt << 1 | 1  
  7. const int maxn = 5555;  
  8. int sum[maxn<<2];  
  9. void PushUP(int rt) {  
  10.     sum[rt] = sum[rt<<1] + sum[rt<<1|1];  
  11. }  
  12. void build(int l,int r,int rt) {  
  13.     sum[rt] = 0;  
  14.     if (l == r) return ;  
  15.     int m = (l + r) >> 1;  
  16.     build(lson);  
  17.     build(rson);  
  18. }  
  19. void update(int p,int l,int r,int rt) {  
  20.     if (l == r) {  
  21.         sum[rt] ++;  
  22.         return ;  
  23.     }  
  24.     int m = (l + r) >> 1;  
  25.     if (p <= m) update(p , lson);  
  26.     else update(p , rson);  
  27.     PushUP(rt);  
  28. }  
  29. int query(int L,int R,int l,int r,int rt) {  
  30.     if (L <= l && r <= R) {  
  31.         return sum[rt];  
  32.     }  
  33.     int m = (l + r) >> 1;  
  34.     int ret = 0;  
  35.     if (L <= m) ret += query(L , R , lson);  
  36.     if (R > m) ret += query(L , R , rson);  
  37.     return ret;  
  38. }  
  39. int x[maxn];  
  40. int main() {  
  41.     int n;  
  42.     while (~scanf("%d",&n)) {  
  43.         build(0 , n - 1 , 1);  
  44.         int sum = 0;  
  45.         for (int i = 0 ; i < n ; i ++) {  
  46.             scanf("%d",&x[i]);  
  47.             sum += query(x[i] , n - 1 , 0 , n - 1 , 1);  
  48.             update(x[i] , 0 , n - 1 , 1);  
  49.         }  
  50.         int ret = sum;  
  51.         for (int i = 0 ; i < n ; i ++) {  
  52.             sum += n - x[i] - x[i] - 1;  
  53.             ret = min(ret , sum);  
  54.         }  
  55.         printf("%d\n",ret);  
  56.     }  
  57.     return 0;  
  58. }  

hdu2795 Billboard
题意:h*w的木板,放进一些1*L的物品,求每次放空间能容纳且最上边的位子
思路:每次找到最大值的位子,然后减去L
线段树功能:query:区间求最大值的位子(直接把update的操作在query里做了)

[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. using namespace std;  
  4.    
  5. #define lson l , m , rt << 1  
  6. #define rson m + 1 , r , rt << 1 | 1  
  7. const int maxn = 222222;  
  8. int h , w , n;  
  9. int MAX[maxn<<2];  
  10. void PushUP(int rt) {  
  11.     MAX[rt] = max(MAX[rt<<1] , MAX[rt<<1|1]);  
  12. }  
  13. void build(int l,int r,int rt) {  
  14.     MAX[rt] = w;  
  15.     if (l == r) return ;  
  16.     int m = (l + r) >> 1;  
  17.     build(lson);  
  18.     build(rson);  
  19. }  
  20. int query(int x,int l,int r,int rt) {  
  21.     if (l == r) {  
  22.         MAX[rt] -= x;  
  23.         return l;  
  24.     }  
  25.     int m = (l + r) >> 1;  
  26.     int ret = (MAX[rt<<1] >= x) ? query(x , lson) : query(x , rson);  
  27.     PushUP(rt);  
  28.     return ret;  
  29. }  
  30. int main() {  
  31.     while (~scanf("%d%d%d",&h,&w,&n)) {  
  32.         if (h > n) h = n;  
  33.         build(1 , h , 1);  
  34.         while (n --) {  
  35.             int x;  
  36.             scanf("%d",&x);  
  37.             if (MAX[1] < x) puts("-1");  
  38.             else printf("%d\n",query(x , 1 , h , 1));  
  39.         }  
  40.     }  
  41.     return 0;  
  42. }  

成段更新(通常这对初学者来说是一道坎),需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候

hdu1698 Just a Hook
题意:O(-1)
思路:O(-1)
线段树功能:update:成段替换 (由于只query一次总区间,所以可以直接输出1结点的信息)
[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. using namespace std;  
  4.    
  5. #define lson l , m , rt << 1  
  6. #define rson m + 1 , r , rt << 1 | 1  
  7. const int maxn = 111111;  
  8. int h , w , n;  
  9. int col[maxn<<2];  
  10. int sum[maxn<<2];  
  11. void PushUp(int rt) {  
  12.     sum[rt] = sum[rt<<1] + sum[rt<<1|1];  
  13. }  
  14. void PushDown(int rt,int m) {  
  15.     if (col[rt]) {  
  16.         col[rt<<1] = col[rt<<1|1] = col[rt];  
  17.         sum[rt<<1] = (m - (m >> 1)) * col[rt];  
  18.         sum[rt<<1|1] = (m >> 1) * col[rt];  
  19.         col[rt] = 0;  
  20.     }  
  21. }  
  22. void build(int l,int r,int rt) {  
  23.     col[rt] = 0;  
  24.     sum[rt] = 1;  
  25.     if (l == r) return ;  
  26.     int m = (l + r) >> 1;  
  27.     build(lson);  
  28.     build(rson);  
  29.     PushUp(rt);  
  30. }  
  31. void update(int L,int R,int c,int l,int r,int rt) {  
  32.     if (L <= l && r <= R) {  
  33.         col[rt] = c;  
  34.         sum[rt] = c * (r - l + 1);  
  35.         return ;  
  36.     }  
  37.     PushDown(rt , r - l + 1);  
  38.     int m = (l + r) >> 1;  
  39.     if (L <= m) update(L , R , c , lson);  
  40.     if (R > m) update(L , R , c , rson);  
  41.     PushUp(rt);  
  42. }  
  43. int main() {  
  44.     int T , n , m;  
  45.     scanf("%d",&T);  
  46.     for (int cas = 1 ; cas <= T ; cas ++) {  
  47.         scanf("%d%d",&n,&m);  
  48.         build(1 , n , 1);  
  49.         while (m --) {  
  50.             int a , b , c;  
  51.             scanf("%d%d%d",&a,&b,&c);  
  52.             update(a , b , c , 1 , n , 1);  
  53.         }  
  54.         printf("Case %d: The total value of the hook is %d.\n",cas , sum[1]);  
  55.     }  
  56.     return 0;  
  57. }  

poj3468 A Simple Problem with Integers
题意:O(-1)
思路:O(-1)
线段树功能:update:成段增减 query:区间求和

[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. using namespace std;  
  4.    
  5. #define lson l , m , rt << 1  
  6. #define rson m + 1 , r , rt << 1 | 1  
  7. #define LL long long  
  8. const int maxn = 111111;  
  9. LL add[maxn<<2];  
  10. LL sum[maxn<<2];  
  11. void PushUp(int rt) {  
  12.     sum[rt] = sum[rt<<1] + sum[rt<<1|1];  
  13. }  
  14. void PushDown(int rt,int m) {  
  15.     if (add[rt]) {  
  16.         add[rt<<1] += add[rt];  
  17.         add[rt<<1|1] += add[rt];  
  18.         sum[rt<<1] += add[rt] * (m - (m >> 1));  
  19.         sum[rt<<1|1] += add[rt] * (m >> 1);  
  20.         add[rt] = 0;  
  21.     }  
  22. }  
  23. void build(int l,int r,int rt) {  
  24.     add[rt] = 0;  
  25.     if (l == r) {  
  26.         scanf("%lld",&sum[rt]);  
  27.         return ;  
  28.     }  
  29.     int m = (l + r) >> 1;  
  30.     build(lson);  
  31.     build(rson);  
  32.     PushUp(rt);  
  33. }  
  34. void update(int L,int R,int c,int l,int r,int rt) {  
  35.     if (L <= l && r <= R) {  
  36.         add[rt] += c;  
  37.         sum[rt] += (LL)c * (r - l + 1);  
  38.         return ;  
  39.     }  
  40.     PushDown(rt , r - l + 1);  
  41.     int m = (l + r) >> 1;  
  42.     if (L <= m) update(L , R , c , lson);  
  43.     if (m < R) update(L , R , c , rson);  
  44.     PushUp(rt);  
  45. }  
  46. LL query(int L,int R,int l,int r,int rt) {  
  47.     if (L <= l && r <= R) {  
  48.         return sum[rt];  
  49.     }  
  50.     PushDown(rt , r - l + 1);  
  51.     int m = (l + r) >> 1;  
  52.     LL ret = 0;  
  53.     if (L <= m) ret += query(L , R , lson);  
  54.     if (m < R) ret += query(L , R , rson);  
  55.     return ret;  
  56. }  
  57. int main() {  
  58.     int N , Q;  
  59.     scanf("%d%d",&N,&Q);  
  60.     build(1 , N , 1);  
  61.     while (Q --) {  
  62.         char op[2];  
  63.         int a , b , c;  
  64.         scanf("%s",op);  
  65.         if (op[0] == 'Q') {  
  66.             scanf("%d%d",&a,&b);  
  67.             printf("%lld\n",query(a , b , 1 , N , 1));  
  68.         } else {  
  69.             scanf("%d%d%d",&a,&b,&c);  
  70.             update(a , b , c , 1 , N , 1);  
  71.         }  
  72.     }  
  73.     return 0;  
  74. }  

poj2528 Mayor’s posters
题意:在墙上贴海报,海报可以互相覆盖,问最后可以看见几张海报
思路:这题数据范围很大,直接搞超时+超内存,需要离散化:
离散化简单的来说就是只取我们需要的值来用,比如说区间[1000,2000],[1990,2012] 我们用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,所以我只需要1000,1990,2000,2012就够了,将其分别映射到0,1,2,3,在于复杂度就大大的降下来了
所以离散化要保存所有需要用到的值,排序后,分别映射到1~n,这样复杂度就会小很多很多
而这题的难点在于每个数字其实表示的是一个单位长度(并非一个点),这样普通的离散化会造成许多错误(包括我以前的代码,poj这题数据奇弱)
给出下面两个简单的例子应该能体现普通离散化的缺陷:
例子一:1-10 1-4 5-10
例子二:1-10 1-4 6-10
普通离散化后都变成了[1,4][1,2][3,4]
线段2覆盖了[1,2],线段3覆盖了[3,4],那么线段1是否被完全覆盖掉了呢?
例子一是完全被覆盖掉了,而例子二没有被覆盖

为了解决这种缺陷,我们可以在排序后的数组上加些处理,比如说[1,2,6,10]
如果相邻数字间距大于1的话,在其中加上任意一个数字,比如加成[1,2,3,6,7,10],然后再做线段树就好了.
线段树功能:update:成段替换 query:简单hash

[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. #include   
  4. using namespace std;  
  5. #define lson l , m , rt << 1  
  6. #define rson m + 1 , r , rt << 1 | 1  
  7.    
  8. const int maxn = 11111;  
  9. bool hash[maxn];  
  10. int li[maxn] , ri[maxn];  
  11. int X[maxn*3];  
  12. int col[maxn<<4];  
  13. int cnt;  
  14.    
  15. void PushDown(int rt) {  
  16.     if (col[rt] != -1) {  
  17.         col[rt<<1] = col[rt<<1|1] = col[rt];  
  18.         col[rt] = -1;  
  19.     }  
  20. }  
  21. void update(int L,int R,int c,int l,int r,int rt) {  
  22.     if (L <= l && r <= R) {  
  23.         col[rt] = c;  
  24.         return ;  
  25.     }  
  26.     PushDown(rt);  
  27.     int m = (l + r) >> 1;  
  28.     if (L <= m) update(L , R , c , lson);  
  29.     if (m < R) update(L , R , c , rson);  
  30. }  
  31. void query(int l,int r,int rt) {  
  32.     if (col[rt] != -1) {  
  33.         if (!hash[col[rt]]) cnt ++;  
  34.         hash[ col[rt] ] = true;  
  35.         return ;  
  36.     }  
  37.     if (l == r) return ;  
  38.     int m = (l + r) >> 1;  
  39.     query(lson);  
  40.     query(rson);  
  41. }  
  42. int Bin(int key,int n,int X[]) {  
  43.     int l = 0 , r = n - 1;  
  44.     while (l <= r) {  
  45.         int m = (l + r) >> 1;  
  46.         if (X[m] == key) return m;  
  47.         if (X[m] < key) l = m + 1;  
  48.         else r = m - 1;  
  49.     }  
  50.     return -1;  
  51. }  
  52. int main() {  
  53.     int T , n;  
  54.     scanf("%d",&T);  
  55.     while (T --) {  
  56.         scanf("%d",&n);  
  57.         int nn = 0;  
  58.         for (int i = 0 ; i < n ; i ++) {  
  59.             scanf("%d%d",&li[i] , &ri[i]);  
  60.             X[nn++] = li[i];  
  61.             X[nn++] = ri[i];  
  62.         }  
  63.         sort(X , X + nn);  
  64.         int m = 1;  
  65.         for (int i = 1 ; i < nn; i ++) {  
  66.             if (X[i] != X[i-1]) X[m ++] = X[i];  
  67.         }  
  68.         for (int i = m - 1 ; i > 0 ; i --) {  
  69.             if (X[i] != X[i-1] + 1) X[m ++] = X[i-1] + 1;  
  70.         }  
  71.         sort(X , X + m);  
  72.         memset(col , -1 , sizeof(col));  
  73.         for (int i = 0 ; i < n ; i ++) {  
  74.             int l = Bin(li[i] , m , X);  
  75.             int r = Bin(ri[i] , m , X);  
  76.             update(l , r , i , 0 , m , 1);  
  77.         }  
  78.         cnt = 0;  
  79.         memset(hash , false , sizeof(hash));  
  80.         query(0 , m , 1);  
  81.         printf("%d\n",cnt);  
  82.     }  
  83.     return 0;  
  84. }  

poj3225 Help with Intervals
题意:区间操作,交,并,补等
思路:
我们一个一个操作来分析:(用0和1表示是否包含区间,-1表示该区间内既有包含又有不包含)
U:把区间[l,r]覆盖成1
I:把[-∞,l)(r,∞]覆盖成0
D:把区间[l,r]覆盖成0
C:把[-∞,l)(r,∞]覆盖成0 , 且[l,r]区间0/1互换
S:[l,r]区间0/1互换

成段覆盖的操作很简单,比较特殊的就是区间0/1互换这个操作,我们可以称之为异或操作
很明显我们可以知道这个性质:当一个区间被覆盖后,不管之前有没有异或标记都没有意义了
所以当一个节点得到覆盖标记时把异或标记清空
而当一个节点得到异或标记的时候,先判断覆盖标记,如果是0或1,直接改变一下覆盖标记,不然的话改变异或标记

开区间闭区间只要数字乘以2就可以处理(偶数表示端点,奇数表示两端点间的区间)
线段树功能:update:成段替换,区间异或 query:简单hash

[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. using namespace std;  
  6. #define lson l , m , rt << 1  
  7. #define rson m + 1 , r , rt << 1 | 1  
  8.    
  9. const int maxn = 131072;  
  10. bool hash[maxn+1];  
  11. int cover[maxn<<2];  
  12. int XOR[maxn<<2];  
  13. void FXOR(int rt) {  
  14.     if (cover[rt] != -1) cover[rt] ^= 1;  
  15.     else XOR[rt] ^= 1;  
  16. }  
  17. void PushDown(int rt) {  
  18.     if (cover[rt] != -1) {  
  19.         cover[rt<<1] = cover[rt<<1|1] = cover[rt];  
  20.         XOR[rt<<1] = XOR[rt<<1|1] = 0;  
  21.         cover[rt] = -1;  
  22.     }  
  23.     if (XOR[rt]) {  
  24.         FXOR(rt<<1);  
  25.         FXOR(rt<<1|1);  
  26.         XOR[rt] = 0;  
  27.     }  
  28. }  
  29. void update(char op,int L,int R,int l,int r,int rt) {  
  30.     if (L <= l && r <= R) {  
  31.         if (op == 'U') {  
  32.             cover[rt] = 1;  
  33.             XOR[rt] = 0;  
  34.         } else if (op == 'D') {  
  35.             cover[rt] = 0;  
  36.             XOR[rt] = 0;  
  37.         } else if (op == 'C' || op == 'S') {  
  38.             FXOR(rt);  
  39.         }  
  40.         return ;  
  41.     }  
  42.     PushDown(rt);  
  43.     int m = (l + r) >> 1;  
  44.     if (L <= m) update(op , L , R , lson);  
  45.     else if (op == 'I' || op == 'C') {  
  46.         XOR[rt<<1] = cover[rt<<1] = 0;  
  47.     }  
  48.     if (m < R) update(op , L , R , rson);  
  49.     else if (op == 'I' || op == 'C') {  
  50.         XOR[rt<<1|1] = cover[rt<<1|1] = 0;  
  51.     }  
  52. }  
  53. void query(int l,int r,int rt) {  
  54.     if (cover[rt] == 1) {  
  55.         for (int it = l ; it <= r ; it ++) {  
  56.             hash[it] = true;  
  57.         }  
  58.         return ;  
  59.     } else if (cover[rt] == 0) return ;  
  60.     if (l == r) return ;  
  61.     PushDown(rt);  
  62.     int m = (l + r) >> 1;  
  63.     query(lson);  
  64.     query(rson);  
  65. }  
  66. int main() {  
  67.     cover[1] = XOR[1] = 0;  
  68.     char op , l , r;  
  69.     int a , b;  
  70.     while ( ~scanf("%c %c%d,%d%c\n",&op , &l , &a , &b , &r) ) {  
  71.         a <<= 1 , b <<= 1;  
  72.         if (l == '(') a ++;  
  73.         if (r == ')') b --;  
  74.         if (a > b) {  
  75.             if (op == 'C' || op == 'I') {  
  76.                 cover[1] = XOR[1] = 0;  
  77.             }  
  78.         } else update(op , a , b , 0 , maxn , 1);  
  79.     }  
  80.     query(0 , maxn , 1);  
  81.     bool flag = false;  
  82.     int s = -1 , e;  
  83.     for (int i = 0 ; i <= maxn ; i ++) {  
  84.         if (hash[i]) {  
  85.             if (s == -1) s = i;  
  86.             e = i;  
  87.         } else {  
  88.             if (s != -1) {  
  89.                 if (flag) printf(" ");  
  90.                 flag = true;  
  91.                 printf("%c%d,%d%c",s&1?'(':'[' , s>>1 , (e+1)>>1 , e&1?')':']');  
  92.                 s = -1;  
  93.             }  
  94.         }  
  95.     }  
  96.     if (!flag) printf("empty set");  
  97.     puts("");  
  98.     return 0;  
  99. }  
练习
poj1436 Horizontally Visible Segments
poj2991 Crane
Another LCIS
Bracket Sequence

区间合并

这类题目会询问区间中满足条件的连续最长区间,所以PushUp的时候需要对左右儿子的区间进行合并
poj3667 Hotel
题意:1 a:询问是不是有连续长度为a的空房间,有的话住进最左边
2 a b:将[a,a+b-1]的房间清空
思路:记录区间中最长的空房间
线段树操作:update:区间替换 query:询问满足条件的最左断点
[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. using namespace std;  
  6. #define lson l , m , rt << 1  
  7. #define rson m + 1 , r , rt << 1 | 1  
  8.    
  9. const int maxn = 55555;  
  10. int lsum[maxn<<2] , rsum[maxn<<2] , msum[maxn<<2];  
  11. int cover[maxn<<2];  
  12.    
  13. void PushDown(int rt,int m) {  
  14.     if (cover[rt] != -1) {  
  15.         cover[rt<<1] = cover[rt<<1|1] = cover[rt];  
  16.         msum[rt<<1] = lsum[rt<<1] = rsum[rt<<1] = cover[rt] ? 0 : m - (m >> 1);  
  17.         msum[rt<<1|1] = lsum[rt<<1|1] = rsum[rt<<1|1] = cover[rt] ? 0 : (m >> 1);  
  18.         cover[rt] = -1;  
  19.     }  
  20. }  
  21. void PushUp(int rt,int m) {  
  22.     lsum[rt] = lsum[rt<<1];  
  23.     rsum[rt] = rsum[rt<<1|1];  
  24.     if (lsum[rt] == m - (m >> 1)) lsum[rt] += lsum[rt<<1|1];  
  25.     if (rsum[rt] == (m >> 1)) rsum[rt] += rsum[rt<<1];  
  26.     msum[rt] = max(lsum[rt<<1|1] + rsum[rt<<1] , max(msum[rt<<1] , msum[rt<<1|1]));  
  27. }  
  28. void build(int l,int r,int rt) {  
  29.     msum[rt] = lsum[rt] = rsum[rt] = r - l + 1;  
  30.     cover[rt] = -1;  
  31.     if (l == r) return ;  
  32.     int m = (l + r) >> 1;  
  33.     build(lson);  
  34.     build(rson);  
  35. }  
  36. void update(int L,int R,int c,int l,int r,int rt) {  
  37.     if (L <= l && r <= R) {  
  38.         msum[rt] = lsum[rt] = rsum[rt] = c ? 0 : r - l + 1;  
  39.         cover[rt] = c;  
  40.         return ;  
  41.     }  
  42.     PushDown(rt , r - l + 1);  
  43.     int m = (l + r) >> 1;  
  44.     if (L <= m) update(L , R , c , lson);  
  45.     if (m < R) update(L , R , c , rson);  
  46.     PushUp(rt , r - l + 1);  
  47. }  
  48. int query(int w,int l,int r,int rt) {  
  49.     if (l == r) return l;  
  50.     PushDown(rt , r - l + 1);  
  51.     int m = (l + r) >> 1;  
  52.     if (msum[rt<<1] >= w) return query(w , lson);  
  53.     else if (rsum[rt<<1] + lsum[rt<<1|1] >= w) return m - rsum[rt<<1] + 1;  
  54.     return query(w , rson);  
  55. }  
  56. int main() {  
  57.     int n , m;  
  58.     scanf("%d%d",&n,&m);  
  59.     build(1 , n , 1);  
  60.     while (m --) {  
  61.         int op , a , b;  
  62.         scanf("%d",&op);  
  63.         if (op == 1) {  
  64.             scanf("%d",&a);  
  65.             if (msum[1] < a) puts("0");  
  66.             else {  
  67.                 int p = query(a , 1 , n , 1);  
  68.                 printf("%d\n",p);  
  69.                 update(p , p + a - 1 , 1 , 1 , n , 1);  
  70.             }  
  71.         } else {  
  72.             scanf("%d%d",&a,&b);  
  73.             update(a , a + b - 1 , 0 , 1 , n , 1);  
  74.         }  
  75.     }  
  76.     return 0;  
  77. }  

练习
hdu3308 LCIS
hdu3397 Sequence operation
hdu2871 Memory Control
hdu1540 Tunnel Warfare
CF46-D Parking Lot

扫描线

这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去
最典型的就是矩形面积并,周长并等题

hdu1542 Atlantis
题意:矩形面积并
思路:浮点数先要离散化;然后把矩形分成两条边,上边和下边,对横轴建树,然后从下到上扫描上去,用cnt表示该区间下边比上边多几个,sum代表该区间内被覆盖的线段的长度总和
这里线段树的一个结点并非是线段的一个端点,而是该端点和下一个端点间的线段,所以题目中r+1,r-1的地方可以自己好好的琢磨一下
线段树操作:update:区间增减 query:直接取根节点的值
[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. using namespace std;  
  6. #define lson l , m , rt << 1  
  7. #define rson m + 1 , r , rt << 1 | 1  
  8.    
  9. const int maxn = 2222;  
  10. int cnt[maxn << 2];  
  11. double sum[maxn << 2];  
  12. double X[maxn];  
  13. struct Seg {  
  14.     double h , l , r;  
  15.     int s;  
  16.     Seg(){}  
  17.     Seg(double a,double b,double c,int d) : l(a) , r(b) , h(c) , s(d) {}  
  18.     bool operator < (const Seg &cmp) const {  
  19.         return h < cmp.h;  
  20.     }  
  21. }ss[maxn];  
  22. void PushUp(int rt,int l,int r) {  
  23.     if (cnt[rt]) sum[rt] = X[r+1] - X[l];  
  24.     else if (l == r) sum[rt] = 0;  
  25.     else sum[rt] = sum[rt<<1] + sum[rt<<1|1];  
  26. }  
  27. void update(int L,int R,int c,int l,int r,int rt) {  
  28.     if (L <= l && r <= R) {  
  29.         cnt[rt] += c;  
  30.         PushUp(rt , l , r);  
  31.         return ;  
  32.     }  
  33.     int m = (l + r) >> 1;  
  34.     if (L <= m) update(L , R , c , lson);  
  35.     if (m < R) update(L , R , c , rson);  
  36.     PushUp(rt , l , r);  
  37. }  
  38. int Bin(double key,int n,double X[]) {  
  39.     int l = 0 , r = n - 1;  
  40.     while (l <= r) {  
  41.         int m = (l + r) >> 1;  
  42.         if (X[m] == key) return m;  
  43.         if (X[m] < key) l = m + 1;  
  44.         else r = m - 1;  
  45.     }  
  46.     return -1;  
  47. }  
  48. int main() {  
  49.     int n , cas = 1;  
  50.     while (~scanf("%d",&n) && n) {  
  51.         int m = 0;  
  52.         while (n --) {  
  53.             double a , b , c , d;  
  54.             scanf("%lf%lf%lf%lf",&a,&b,&c,&d);  
  55.             X[m] = a;  
  56.             ss[m++] = Seg(a , c , b , 1);  
  57.             X[m] = c;  
  58.             ss[m++] = Seg(a , c , d , -1);  
  59.         }  
  60.         sort(X , X + m);  
  61.         sort(ss , ss + m);  
  62.         int k = 1;  
  63.         for (int i = 1 ; i < m ; i ++) {  
  64.             if (X[i] != X[i-1]) X[k++] = X[i];  
  65.         }  
  66.         memset(cnt , 0 , sizeof(cnt));  
  67.         memset(sum , 0 , sizeof(sum));  
  68.         double ret = 0;  
  69.         for (int i = 0 ; i < m - 1 ; i ++) {  
  70.             int l = Bin(ss[i].l , k , X);  
  71.             int r = Bin(ss[i].r , k , X) - 1;  
  72.             if (l <= r) update(l , r , ss[i].s , 0 , k - 1, 1);  
  73.             ret += sum[1] * (ss[i+1].h - ss[i].h);  
  74.         }  
  75.         printf("Test case #%d\nTotal explored area: %.2lf\n\n",cas++ , ret);  
  76.     }  
  77.     return 0;  
  78. }  

hdu1828 Picture
题意:矩形周长并
思路:与面积不同的地方是还要记录竖的边有几个(numseg记录),并且当边界重合的时候需要合并(用lbd和rbd表示边界来辅助)
线段树操作:update:区间增减 query:直接取根节点的值

[cpp]  view plain  copy
 print ?
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. using namespace std;  
  6. #define lson l , m , rt << 1  
  7. #define rson m + 1 , r , rt << 1 | 1  
  8.    
  9. const int maxn = 22222;  
  10. struct Seg{  
  11.     int l , r , h , s;  
  12.     Seg() {}  
  13.     Seg(int a,int b,int c,int d):l(a) , r(b) , h(c) , s(d) {}  
  14.     bool operator < (const Seg &cmp) const {  
  15.         if (h == cmp.h) return s > cmp.s;  
  16.         return h < cmp.h;  
  17.     }  
  18. }ss[maxn];  
  19. bool lbd[maxn<<2] , rbd[maxn<<2];  
  20. int numseg[maxn<<2];  
  21. int cnt[maxn<<2];  
  22. int len[maxn<<2];  
  23. void PushUP(int rt,int l,int r) {  
  24.     if (cnt[rt]) {  
  25.         lbd[rt] = rbd[rt] = 1;  
  26.         len[rt] = r - l + 1;  
  27.         numseg[rt] = 2;  
  28.     } else if (l == r) {  
  29.         len[rt] = numseg[rt] = lbd[rt] = rbd[rt] = 0;  
  30.     } else {  
  31.         lbd[rt] = lbd[rt<<1];  
  32.         rbd[rt] = rbd[rt<<1|1];  
  33.         len[rt] = len[rt<<1] + len[rt<<1|1];  
  34.         numseg[rt] = numseg[rt<<1] + numseg[rt<<1|1];  
  35.         if (lbd[rt<<1|1] && rbd[rt<<1]) numseg[rt] -= 2;//两条线重合  
  36.     }  
  37. }  
  38. void update(int L,int R,int c,int l,int r,int rt) {  
  39.     if (L <= l && r <= R) {  
  40.         cnt[rt] += c;  
  41.         PushUP(rt , l , r);  
  42.         return ;  
  43.     }  
  44.     int m = (l + r) >> 1;  
  45.     if (L <= m) update(L , R , c , lson);  
  46.     if (m < R) update(L , R , c , rson);  
  47.     PushUP(rt , l , r);  
  48. }  
  49. int main() {  
  50.     int n;  
  51.     while (~scanf("%d",&n)) {  
  52.         int m = 0;  
  53.         int lbd = 10000, rbd = -10000;  
  54.         for (int i = 0 ; i < n ; i ++) {  
  55.             int a , b , c , d;  
  56.             scanf("%d%d%d%d",&a,&b,&c,&d);  
  57.             lbd = min(lbd , a);  
  58.             rbd = max(rbd , c);  
  59.             ss[m++] = Seg(a , c , b , 1);  
  60.             ss[m++] = Seg(a , c , d , -1);  
  61.         }  
  62.         sort(ss , ss + m);  
  63.         int ret = 0 , last = 0;  
  64.         for (int i = 0 ; i < m ; i ++) {  
  65.             if (ss[i].l < ss[i].r) update(ss[i].l , ss[i].r - 1 , ss[i].s , lbd , rbd - 1 , 1);  
  66.             ret += numseg[1] * (ss[i+1].h - ss[i].h);  
  67.             ret += abs(len[1] - last);  
  68.             last = len[1];  
  69.         }  
  70.         printf("%d\n",ret);  
  71.     }  
  72.     return 0;  
  73. }  

练习
hdu3265 Posters
hdu3642 Get The Treasury
poj2482 Stars in Your Window
poj2464 Brownie Points II
hdu3255 Farming 
ural1707 Hypnotoad’s Secret
uva11983 Weird Advertisement

多颗线段树问题

此类题目主用特点是区间不连续,有一定规律间隔,用多棵树表示不同的偏移区间
hdu 4288 coder
题意:
维护一个有序数列{An},有三种操作:
1、添加一个元素。
2、删除一个元素。
3、求数列中下标%5 = 3的值的和。

由于有删除和添加操作,所以离线离散操作,节点中cnt存储区间中有几个数,sum存储偏移和
[cpp]  view plain  copy
 print ?
  1. #include  
  2. #include  
  3. #include  
  4. #include  
  5. using namespace std;  
  6. const int maxn=100002;  
  7.   
  8. #define lson l , m , rt << 1    
  9. #define rson m + 1 , r , rt << 1 | 1   
  10.   
  11. __int64 sum[maxn<<2][6];  
  12. int cnt[maxn << 2];  
  13.   
  14. char op[maxn][20];  
  15. int a[maxn];  
  16.   
  17. int X[maxn];  
  18.   
  19. void PushUp(int rt)  
  20. {  
  21.     cnt[rt] = cnt[rt<<1] + cnt[rt<<1|1];  
  22.       
  23.     int offset = cnt[rt<<1];  
  24.     for(int i = 0; i < 5; ++i)  
  25.     {  
  26.         sum[rt][i] = sum[rt<<1][i];  
  27.     }  
  28.     for(int i = 0; i < 5; ++i)  
  29.     {  
  30.         sum[rt][(i + offset) % 5] += sum[rt<<1|1][i];  
  31.     }  
  32. }  
  33.   
  34. void Build(int l, int r, int rt)    
  35. {   /*此题Build完全可以用一个memset代替*/  
  36.     cnt[rt] = 0;  
  37.     for(int i = 0; i < 5; ++i)   sum[rt][i] = 0;  
  38.     if( l == r ) return;  
  39.     int m = ( l + r )>>1;      
  40.     Build(lson);    
  41.     Build(rson);     
  42. }   
  43.   
  44. void Updata(int p, int op, int l, int r, int rt)    
  45. {     
  46.     if( l == r )    
  47.     {    
  48.         cnt[rt] = op;   
  49.         sum[rt][1] = op * X[l-1];   
  50.         return ;    
  51.     }    
  52.     int m = ( l + r ) >> 1;    
  53.     if(p <= m)    
  54.         Updata(p, op, lson);    
  55.     else    
  56.         Updata(p, op, rson);    
  57.     
  58.     PushUp(rt);    
  59. }   
  60.   
  61. int main()  
  62. {  
  63.     int n;  
  64.     while(scanf("%d", &n) != EOF)  
  65.     {  
  66.         int nn = 0;  
  67.         for(int i = 0; i < n; ++i)  
  68.         {  
  69.             scanf("%s", &op[i]);  
  70.               
  71.             if(op[i][0] != 's')  
  72.             {  
  73.                 scanf("%d", &a[i]);  
  74.                 if(op[i][0] == 'a')  
  75.                 {  
  76.                     X[nn++] = a[i];  
  77.                 }  
  78.             }  
  79.         }  
  80.           
  81.         sort(X,X+nn);/*unique前必须sort*/  
  82.         nn = unique(X, X + nn) - X; /*去重并得到总数*/  
  83.           
  84.         Build(1, nn, 1);  
  85.           
  86.         for(int i = 0; i < n; ++i)  
  87.         {  
  88.             int pos = upper_bound(X, X+nn, a[i]) - X; /* hash */   
  89.             if(op[i][0] == 'a')  
  90.             {  
  91.                 Updata(pos, 1, 1, nn, 1);  
  92.             }  
  93.             else if(op[i][0] == 'd')  
  94.             {  
  95.                 Updata(pos, 0, 1, nn, 1);  
  96.             }  
  97.             else printf("%I64d\n",sum[1][3]);  
  98.         }  
  99.     }  
  100.     return 0;  
  101. }  

2: hdu 4267 A simple problem with integers
题目:给出n个数,每次将一段区间内满足(i-l)%k==0  (r>=i>=l) 的数ai增加c, 最后单点查询。
这种题目更新的区间是零散的,如果可以通过某种方式让离散的都变得连续,那么问题就可以用线段树完美解决。解决方式一般也是固定的,那就是利用题意维护多颗线段树。此题虚维护55颗,更新最终确定在一颗上,查询则将查询点被包含的树全部叠加。
[cpp]  view plain  copy
 print ?
  1. #include  
  2. #include  
  3. #include  
  4. #include  
  5. #include  
  6. #include  
  7. #include  
  8. #include  
  9. #include  
  10. #define eps 1e-7  
  11. #define LL long long  
  12. #define N 500005  
  13. #define zero(a) fabs(a)  
  14. #define lson step<<1  
  15. #define rson step<<1|1  
  16. #define MOD 1234567891  
  17. #define pb(a) push_back(a)  
  18. using namespace std;  
  19. struct Node{  
  20.     int left,right,add[55],sum;  
  21.     int mid(){return (left+right)/2;}  
  22. }L[4*N];  
  23. int a[N],n,b[11][11];  
  24. void Bulid(int step ,int l,int r){  
  25.     L[step].left=l;  
  26.     L[step].right=r;  
  27.     L[step].sum=0;  
  28.     memset(L[step].add,0,sizeof(L[step].add));  
  29.     if(l==r) return ;  
  30.     Bulid(lson,l,L[step].mid());  
  31.     Bulid(rson,L[step].mid()+1,r);  
  32. }  
  33. void push_down(int step){  
  34.     if(L[step].sum){  
  35.         L[lson].sum+=L[step].sum;  
  36.         L[rson].sum+=L[step].sum;  
  37.         L[step].sum=0;  
  38.         for(int i=0;i<55;i++){  
  39.                 L[lson].add[i]+=L[step].add[i];  
  40.                 L[rson].add[i]+=L[step].add[i];  
  41.                 L[step].add[i]=0;  
  42.         }  
  43.     }  
  44. }  
  45. void update(int step,int l,int r,int num,int i,int j){  
  46.     if(L[step].left==l&&L[step].right==r){  
  47.         L[step].sum+=num;  
  48.         L[step].add[b[i][j]]+=num;  
  49.         return;  
  50.     }  
  51.     push_down(step);  
  52.     if(r<=L[step].mid()) update(lson,l,r,num,i,j);  
  53.     else if(l>L[step].mid()) update(rson,l,r,num,i,j);  
  54.     else {  
  55.         update(lson,l,L[step].mid(),num,i,j);  
  56.         update(rson,L[step].mid()+1,r,num,i,j);  
  57.     }  
  58. }  
  59. int query(int step,int pos){  
  60.     if(L[step].left==L[step].right){  
  61.         int tmp=0;  
  62.         for(int i=1;i<=10;i++)  tmp+=L[step].add[b[i][pos%i]];  
  63.         return a[L[step].left]+tmp;  
  64.     }  
  65.     push_down(step);  
  66.     if(pos<=L[step].mid()) return query(lson,pos);  
  67.     else return query(rson,pos);  
  68. }  
  69. int main(){  
  70.     int cnt=0;  
  71.     for(int i=1;i<=10;i++) for(int j=0;j
  72.     while(scanf("%d",&n)!=EOF){  
  73.         for(int i=1;i<=n;i++) scanf("%d",&a[i]);  
  74.         Bulid(1,1,n);  
  75.         int q,d;  
  76.         scanf("%d",&q);  
  77.         while(q--){  
  78.             int k,l,r,m;  
  79.             scanf("%d",&k);  
  80.             if(k==2){  
  81.                 scanf("%d",&m);  
  82.                 printf("%d\n",query(1,m));  
  83.             }  
  84.             else{  
  85.                 scanf("%d%d%d%d",&l,&r,&d,&m);  
  86.                 update(1,l,r,m,d,l%d);  
  87.             }  
  88.         }  
  89.     }  
  90.     return 0;  
  91. }  




线段树与其他结合练习(欢迎大家补充):

  • hdu3954 Level up
  • hdu4027 Can you answer these queries?
  • hdu3333 Turing Tree
  • hdu3874 Necklace
  • hdu3016 Man Down
  • hdu3340 Rain in ACStar
  • zju3511 Cake Robbery
  • UESTC1558 Charitable Exchange
  • CF85-D Sum of Medians
  • spojGSS2 Can you answer these queries II


49
1
 
 

我的同类文章

ACM回忆(46)
  • 天马行空的ACM现场赛回顾2013-11-13阅读2103
  • hdu 4711 Weather概率dp2013-09-20阅读1017
  • hdu 4722 GoodNumbers2013-09-19阅读788
  • 动态规划-各种整数划分2013-09-01阅读1269
  • Three Swaps DFS2013-08-31阅读839
  • 单点更新线段树 RMQ2013-08-27阅读894
  • Simple Template2013-10-20阅读977
  • 数据结构---各种树模板 持续更新···2013-09-20阅读1994
  • hdu 4712 Hamming distance2013-09-19阅读959
  • Book of Evil 树双向DFS2013-08-31阅读1137
  • 双向DFS模板题2013-08-30阅读821
更多文章
猜你在找
顾荣:开源大数据存储系统Alluxio(原Tachyon)的原理分析与案例简介
数据结构(C版)
Ceph—分布式存储系统的另一个选择
Docker与容器服务扩展机制 - 存储
数据结构基础系列(5):数组与广义表
cdoj 2015数据结构专题M - 秋实大哥与线段树
CUGB专题训练之数据结构B - Count Color 线段树区间更新
数据结构线段树单点更新
数据结构线段树详解超详细
活用各种数据结构线段树篇
查看评论
26楼  消磨时间 2016-08-10 16:19发表 [回复]
请问,为什么要用四倍的空间。谢谢你。
25楼  bighero4 2016-06-26 21:26发表 [回复]
看明白了,那个build的例子,每个区间节点的value代表此区间(在数组array中是下标范围)上的的数组array的最小值。
24楼  bighero4 2016-06-26 21:15发表 [回复]
把所有错误纠正一下吧。比如说:线段树并非完全二叉树。
有些地方做一下解释,比如Build中的Value是什么意思?
23楼  Sci_M3 2016-06-08 11:42发表 [回复]
模板一是有错的,顺便这个错就是您自己跑的样例的问题...
22楼  zlqdhrdhrdhr 2015-11-29 09:27发表 [回复]
博主第3块代码有个小错误
那个left跟right应该是begin和end吧 ^_^
Re:  wyj__jenny 2016-02-04 11:39发表 [回复]
回复zlqdhrdhrdhr:同意
21楼  清风小竹 2015-11-03 16:12发表 [回复]
楼主的博客写的很好!赞一个
20楼  liangshanxiaohan 2015-10-28 21:01发表 [回复]
楼主你好,我想请问一下,你是否接触过range tree,能不能给一段range tree建树的代码
19楼  Lormons 2015-09-27 11:43发表 [回复]
不是完全二叉树吧...~~
18楼  LuckyqXd 2015-08-22 23:22发表 [回复]
线段树可以不为完全二叉树。。。。
17楼  bestcoder_judge 2015-08-21 15:44发表 [回复]
不错不错,线段树估计你没问题了!
16楼  Triangle_libing 2015-08-12 10:28发表 [回复]
这跟编译器有什么关系
15楼  Anger_Coder 2015-07-03 18:46发表 [回复]
有一个点需要注意,mid的求法,不要(begin+end)/2这个会溢出,要begin + (end - begin) >> 1 这样比较好
Re:  glassMonster 2015-11-17 19:15发表 [回复]
回复Anger_Coder:不过需要注意一点,移位运算符的优先级低,在这里就比“+”低,最后的结果就是end>>1,会使程序运行出错,所以最好再加括号begin+((end-begin)>>1)
14楼  小飞哇咔咔 2015-04-01 12:01发表 [回复]
mark
13楼  ouweiqi 2015-03-30 11:13发表 [回复]
错误百出,还好意思贴出来
Re:  Echizens 2015-08-20 15:16发表 [回复]
回复ouweiqi:至少人家很谦虚。所以有意见提出来,不要BB。
12楼  楼上小宇 2015-01-27 23:43发表 [回复]
受教了。。。很详细
11楼  MetalSeed 2014-11-12 16:09发表 [回复]
228003430
10楼  累了泪了funny 2014-07-25 20:53发表 [回复]
为什么都不喜欢定义结构体呢,表示用数组模拟,看不懂,不要嘲笑我,我是菜鸟
Re:  MetalSeed 2014-11-07 09:46发表 [回复]
回复累了泪了funny:那个时候强迫症,数组写起来方便一些,速度稍微快一点
9楼  MetalSeed 2013-01-01 23:50发表 [回复]
http://blog.csdn.net/shiqi_614/article/details/8228102
8楼  GodHunter47 2012-12-31 15:16发表 [回复]
弱弱的屌丝一枚,不过勉为其难的给你顶一下
Re:  MetalSeed 2013-01-01 23:47发表 [回复]
回复GodHunter47:```` ~~ 呼呼 谢谢大神
7楼  Zr777111 2012-10-12 15:11发表 [回复]
高深。。。
6楼  wd339092886 2012-10-12 14:09发表 [回复]
不行不行,我还是倾向于编程。。
5楼  dyx心心 2012-10-12 11:32发表 [回复]
楼主是搞ACM的?
Re:  MetalSeed 2012-10-12 23:38发表 [回复]
回复dyx心心:ACM弱菜一枚.. 正在努力中..
Re:  dyx心心 2012-11-19 09:37发表 [回复]
回复MetalSeed:我感觉最后那道题,hdu4267那个sum就是个标记而已,不需要
L[step].sum+=num; 只要 L[step].sum=1,就行了吧
4楼  c_006 2012-10-11 11:31发表 [回复]
mark一下。楼主的编译软件是什么的?这么花花绿绿的vs2010?
Re:  mz31098 2015-08-22 03:10发表 [回复]
回复c_006:看着像sublime
Re:  hellosijian 2012-10-11 19:46发表 [回复]
回复c_006:应该是vim
Re:  hello_world_ww 2013-01-26 09:34发表 [回复]
回复hellosijian:GCC/G++
3楼  jiangkaii 2012-10-11 07:32发表 [回复]
进首页了!!
Re:  MetalSeed 2012-10-11 10:51发表 [回复]
回复jiangkaii:是蒋凯么。。。
Re:  jiangkaii 2012-10-12 01:51发表 [回复]
回复MetalSeed:是的,
2楼  pl___ 2012-10-09 16:16发表 [回复]
手动实现std::map ?
Re:  MetalSeed 2012-10-09 17:36发表 [回复]
回复pl___:非也。
map是用红黑树实现的,特点是插入时按照键值自动排序
1楼  Dstnoe 2012-10-08 23:22发表 [回复]
请问,线段树和B树在应用上有什么特别之处?
Re:  MetalSeed 2012-10-09 17:38发表 [回复]
回复Dstnoe:不好意思..B树只有耳闻...没详细研究过... 还得努力呢
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
核心技术类目
全部主题  Hadoop  AWS  移动游戏  Java  Android  iOS  Swift  智能硬件  Docker  OpenStack  VPN  Spark  ERP IE10  Eclipse  CRM  JavaScript  数据库  Ubuntu  NFC  WAP  jQuery  BI  HTML5  Spring  Apache  .NET  API HTML  SDK  IIS  Fedora  XML  LBS  Unity  Splashtop  UML  components  Windows Mobile  Rails  QEMU  KDE Cassandra  CloudStack  FTC  coremail  OPhone  CouchBase  云计算  iOS6  Rackspace  Web App  SpringSide  Maemo Compuware  大数据  aptech  Perl  Tornado  Ruby  Hibernate  ThinkPHP  HBase  Pure  Solr  Angular  Cloud Foundry Redis  Scala  Django  Bootstrap
  • 个人资料
  •  
    MetalSeed
     
    • 访问:357056次
    • 积分:4370
    • 等级: 
    • 排名:第4875名
    • 原创:109篇
    • 转载:22篇
    • 译文:2篇
    • 评论:154条
  • 文章分类
  • 处理器Ambarella(1)
  • 嵌入式Android(5)
  • 嵌入式linux(20)
  • STM32笔记(14)
  • ACM回忆(47)
  • MCU相关(27)
  • 随笔 小记(2)
  • 东西 小站(6)
  • 文章存档
    • 2016年07月(1)
    • 2015年06月(1)
    • 2015年05月(1)
    • 2015年04月(3)
    • 2015年03月(4)
      展开
  • 阅读排行
  • 数据结构专题——线段树(60959)
  • 主席树介绍(25025)
  • NE555 + CD4017流水灯(20494)
  • Android蓝牙串口通信模板及demo,trick(16773)
  • 两款主流摄像头OV7620与OV7670 By Demok(14401)
  • 锁存器使用总结(9016)
  • D/A与A/D转换器(6283)
  • 各种音频视频编码方法(6085)
  • STM32F407 Discovery uart1串口通信(5760)
  • 基于51的爱心流水灯源码(4698)
  • 评论排行
  • 数据结构专题——线段树(40)
  • 主席树介绍(14)
  • Android蓝牙串口通信模板及demo,trick(11)
  • 天马行空的ACM现场赛回顾(8)
  • 51单片机进阶(6)
  • 51单片机导论(6)
  • STM32F051 IAP源码分享(6)
  • 基于51的爱心流水灯源码(5)
  • 基于RS485的简单现场总线通信系统设计-南邮自动化课程设计(4)
  • C代码优化方案(4)
  • 推荐文章
    • * 2016 年最受欢迎的编程语言是什么?
    • * Chromium扩展(Extension)的页面(Page)加载过程分析
    • * Android Studio 2.2 来啦
    • * 手把手教你做音乐播放器(二)技术原理与框架设计
    • * JVM 性能调优实战之:使用阿里开源工具 TProfiler 在海量业务代码中精确定位性能代码
  • 最新评论
  • 数据结构专题——线段树

    消磨时间: 请问,为什么要用四倍的空间。谢谢你。

  • 主席树介绍

    独立团团长李云龙: 至于么 应该是sublime或者是notepad++吧……此代码经检验有毒

  • 数据结构专题——线段树

    bighero4: 看明白了,那个build的例子,每个区间节点的value代表此区间(在数组array中是下标范围)上...

  • 数据结构专题——线段树

    bighero4: 把所有错误纠正一下吧。比如说:线段树并非完全二叉树。有些地方做一下解释,比如Build中的Value...

  • 数据结构专题——线段树

    Sci_M3: 模板一是有错的,顺便这个错就是您自己跑的样例的问题...

  • 天马行空的ACM现场赛回顾

    : good

  • 基于RS485的简单现场总线通信系统设计-南邮自动化课程设计

    njuptbd13: 报告书求分享,[email protected]

  • 基于RS485的简单现场总线通信系统设计-南邮自动化课程设计

    njuptbd13: 博主有没有整个工程的报告书,渴求分享。

  • 基于RS485的简单现场总线通信系统设计-南邮自动化课程设计

    njuptbd13: 博主有没有基于RS485的简单现场总线通信系统设计的报告书

  • STM32F051 IAP源码分享

    dayunshixiong: 博主,发我整个工程可以吗,谢谢拉


 

数据结构专题——线段树

标签: buildquery存储c
  61010人阅读  评论(40)  收藏  举报
本文章已收录于: 
 C语言知识库
  分类:
ACM回忆(46) 

目录(?)[+]

线段树


转载请注明出处,谢谢!http://blog.csdn.net/metalseed/article/details/8039326 

持续更新中···


一:线段树基本概念

1:概述

线段树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(lgN)!

性质:父亲的区间是[a,b],(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b],线段树需要的空间为数组大小的四倍


2:基本操作(demo用的是查询区间最小值)

线段树的主要操作有:

(1):线段树的构造 void build(int node, int begin, int end);

主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,最后回溯的时候给当前节点赋值

[cpp]  view plain  copy
 print ?
  1. #include   
  2. using namespace std;  
  3.   
  4. const int maxind = 256;  
  5. int segTree[maxind * 4 + 10];  
  6. int array[maxind];   
  7. /* 构造函数,得到线段树 */  
  8. void build(int node, int begin, int end)    
  9. {    
  10.     if (begin == end)    
  11.         segTree[node] = array[begin]; /* 只有一个元素,节点记录该单元素 */  
  12.     else    
  13.     {     
  14.         /* 递归构造左右子树 */   
  15.         build(2*node, begin, (begin+end)/2);    
  16.         build(2*node+1, (begin+end)/2+1, end);   
  17.            
  18.         /* 回溯时得到当前node节点的线段信息 */    
  19.         if (segTree[2 * node] <= segTree[2 * node + 1])    
  20.             segTree[node] = segTree[2 * node];    
  21.         else    
  22.             segTree[node] = segTree[2 * node + 1];    
  23.     }    
  24. }  
  25.   
  26. int main()  
  27. {  
  28.     array[0] = 1, array[1] = 2,array[2] = 2, array[3] = 4, array[4] = 1, array[5] = 3;  
  29.     build(1, 0, 5);  
  30.     for(int i = 1; i<=20; ++i)  
  31.      cout<< "seg"<< i << "=" <
  32.     return 0;  
  33. }   
 此build构造成的树如图:

(2):区间查询int query(int node, int begin, int end, int left, int right);

(其中node为当前查询节点,begin,end为当前节点存储的区间,left,right为此次query所要查询的区间)

主要思想是把所要查询的区间[a,b]划分为线段树上的节点,然后将这些节点代表的区间合并起来得到所需信息

比如前面一个图中所示的树,如果询问区间是[0,2],或者询问的区间是[3,3],不难直接找到对应的节点回答这一问题。但并不是所有的提问都这么容易回答,比如[0,3],就没有哪一个节点记录了这个区间的最小值。当然,解决方法也不难找到:把[0,2][3,3]两个区间(它们在整数意义上是相连的两个区间)的最小值合并起来,也就是求这两个最小值的最小值,就能求出[0,3]范围的最小值。同理,对于其他询问的区间,也都可以找到若干个相连的区间,合并后可以得到询问的区间。

[cpp]  view plain  copy
 print ?
  1. int query(int node, int begin, int end, int left, int right)    
  2. {   
  3.     int p1, p2;    
  4.     
  5.     /*  查询区间和要求的区间没有交集  */  
  6.     if (left > end || right < begin)    
  7.         return -1;    
  8.     
  9.     /*  if the current interval is included in  */    
  10.     /*  the query interval return segTree[node]  */  
  11.     if (begin >= left && end <= right)    
  12.         return segTree[node];    
  13.     
  14.     /*  compute the minimum position in the  */  
  15.     /*  left and right part of the interval  */   
  16.     p1 = query(2 * node, begin, (begin + end) / 2, left, right);   
  17.     p2 = query(2 * node + 1, (begin + end) / 2 + 1, end, left, right);    
  18.     
  19.     /*  return the expect value  */   
  20.     if (p1 == -1)    
  21.         return p2;    
  22.     if (p2 == -1)    
  23.         return p1;    
  24.     if (p1 <= p2)    
  25.         return  p1;    
  26.     return  p2;      
  27. }   

可见,这样的过程一定选出了尽量少的区间,它们相连后正好涵盖了整个[left,right],没有重复也没有遗漏。同时,考虑到线段树上每层的节点最多会被选取2个,一共选取的节点数也是O(log n)的,因此查询的时间复杂度也是O(log n)。

线段树并不适合所有区间查询情况,它的使用条件是“相邻的区间的信息可以被合并成两个区间的并区间的信息”。即问题是可以被分解解决的。



(3):区间或节点的更新 及 线段树的动态维护update (这是线段树核心价值所在,节点中的标记域可以解决N多种问题)

动态维护需要用到标记域,延迟标记等。

a:单节点更新

[cpp]  view plain  copy
 print ?
  1. void Updata(int node, int begin, int end, int ind, int add)/*单节点更新*/    
  2. {    
  3.     
  4.     if( begin == end )    
  5.     {    
  6.         segTree[node] += add;    
  7.         return ;    
  8.     }    
  9.     int m = ( left + right ) >> 1;    
  10.     if(ind <= m)    
  11.         Updata(node * 2,left, m, ind, add);    
  12.     else    
  13.         Updata(node * 2 + 1, m + 1, right, ind, add);    
  14.     /*回溯更新父节点*/    
  15.     segTree[node] = min(segTree[node * 2], segTree[node * 2 + 1]);     
  16.          
  17. }   

b:区间更新(线段树中最有用的)

需要用到延迟标记,每个结点新增加一个标记,记录这个结点是否被进行了某种修改操作(这种修改操作会影响其子结点)。对于任意区间的修改,我们先按照查询的方式将其划分成线段树中的结点,然后修改这些结点的信息,并给这些结点标上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个结点p,并且决定考虑其子结点,那么我们就要看看结点p有没有标记,如果有,就要按照标记修改其子结点的信息,并且给子结点都标上相同的标记,同时消掉p的标记。(优点在于,不用将区间内的所有值都暴力更新,大大提高效率,因此区间更新是最优用的操作)

void Change来自dongxicheng.org

[cpp]  view plain  copy
 print ?
  1. void Change(node *p, int a, int b) /* 当前考察结点为p,修改区间为(a,b]*/  
  2.    
  3. {  
  4.    
  5.   if (a <= p->Left && p->Right <= b)  
  6.    
  7.   /* 如果当前结点的区间包含在修改区间内*/  
  8.    
  9.   {  
  10.    
  11.      ...... /* 修改当前结点的信息,并标上标记*/  
  12.    
  13.      return;  
  14.    
  15.   }  
  16.    
  17.   Push_Down(p); /* 把当前结点的标记向下传递*/  
  18.    
  19.   int mid = (p->Left + p->Right) / 2; /* 计算左右子结点的分隔点 
  20.   
  21.   if (a < mid) Change(p->Lch, a, b); /* 和左孩子有交集,考察左子结点*/  
  22.    
  23.   if (b > mid) Change(p->Rch, a, b); /* 和右孩子有交集,考察右子结点*/  
  24.    
  25.   Update(p); /* 维护当前结点的信息(因为其子结点的信息可能有更改)*/  
  26.    
  27. }  



3:主要应用

(1):区间最值查询问题 (见模板1)

(2):连续区间修改或者单节点更新的动态查询问题 (见模板2)

(3):多维空间的动态查询 (见模板3)


二:典型模板

模板1:

RMQ,查询区间最值下标---min

[cpp]  view plain  copy
 print ?
  1. #include    
  2.   
  3. using namespace std;    
  4.     
  5. #define MAXN 100    
  6. #define MAXIND 256 //线段树节点个数    
  7.     
  8. //构建线段树,目的:得到M数组.    
  9. void build(int node, int b, int e, int M[], int A[])    
  10. {    
  11.     if (b == e)    
  12.         M[node] = b; //只有一个元素,只有一个下标    
  13.     else    
  14.     {     
  15.         build(2 * node, b, (b + e) / 2, M, A);    
  16.         build(2 * node + 1, (b + e) / 2 + 1, e, M, A);    
  17.   
  18.         if (A[M[2 * node]] <= A[M[2 * node + 1]])    
  19.             M[node] = M[2 * node];    
  20.         else    
  21.             M[node] = M[2 * node + 1];    
  22.     }    
  23. }    
  24.     
  25. //找出区间 [i, j] 上的最小值的索引    
  26. int query(int node, int b, int e, int M[], int A[], int i, int j)    
  27. {    
  28.     int p1, p2;    
  29.     
  30.     //查询区间和要求的区间没有交集    
  31.     if (i > e || j < b)    
  32.         return -1;    
  33.   
  34.     if (b >= i && e <= j)    
  35.         return M[node];    
  36.    
  37.     p1 = query(2 * node, b, (b + e) / 2, M, A, i, j);    
  38.     p2 = query(2 * node + 1, (b + e) / 2 + 1, e, M, A, i, j);    
  39.     
  40.     //return the position where the overall    
  41.     //minimum is    
  42.     if (p1 == -1)    
  43.         return M[node] = p2;    
  44.     if (p2 == -1)    
  45.         return M[node] = p1;    
  46.     if (A[p1] <= A[p2])    
  47.         return M[node] = p1;    
  48.     return M[node] = p2;    
  49.     
  50. }    
  51.     
  52.     
  53. int main()    
  54. {    
  55.     int M[MAXIND]; //下标1起才有意义,否则不是二叉树,保存下标编号节点对应区间最小值的下标.    
  56.     memset(M,-1,sizeof(M));    
  57.     int a[]={3,4,5,7,2,1,0,3,4,5};    
  58.     build(1, 0, sizeof(a)/sizeof(a[0])-1, M, a);    
  59.     cout<sizeof(a)/sizeof(a[0])-1, M, a, 0, 5)<
  60.     return 0;    
  61. }    



模板2:

连续区间修改或者单节点更新的动态查询问题 (此模板查询区间和)

[cpp]  view plain  copy
 print ?
  1. #include     
  2. #include     
  3. using namespace std;    
  4.      
  5. #define lson l , m , rt << 1    
  6. #define rson m + 1 , r , rt << 1 | 1   
  7. #define root 1 , N , 1   
  8. #define LL long long    
  9. const int maxn = 111111;    
  10. LL add[maxn<<2];    
  11. LL sum[maxn<<2];    
  12. void PushUp(int rt) {    
  13.     sum[rt] = sum[rt<<1] + sum[rt<<1|1];    
  14. }    
  15. void PushDown(int rt,int m) {    
  16.     if (add[rt]) {    
  17.         add[rt<<1] += add[rt];    
  18.         add[rt<<1|1] += add[rt];    
  19.         sum[rt<<1] += add[rt] * (m - (m >> 1));    
  20.         sum[rt<<1|1] += add[rt] * (m >> 1);    
  21.         add[rt] = 0;    
  22.     }    
  23. }    
  24. void build(int l,int r,int rt) {    
  25.     add[rt] = 0;    
  26.     if (l == r) {    
  27.         scanf("%lld",&sum[rt]);    
  28.         return ;    
  29.     }    
  30.     int m = (l + r) >> 1;    
  31.     build(lson);    
  32.     build(rson);    
  33.     PushUp(rt);    
  34. }    
  35. void update(int L,int R,int c,int l,int r,int rt) {    
  36.     if (L <= l && r <= R) {    
  37.         add[rt] += c;    
  38.         sum[rt] += (LL)c * (r - l + 1);    
  39.         return ;    
  40.     }    
  41.     PushDown(rt , r - l + 1);    
  42.     int m = (l + r) >> 1;    
  43.     if (L <= m) update(L , R , c , lson);    
  44.     if (m < R) update(L , R , c , rson);    
  45.     PushUp(rt);    
  46. }    
  47. LL query(int L,int R,int l,int r,int rt) {    
  48.     if (L <= l && r <= R) {    
  49.         return sum[rt];    
  50.     }    
  51.     PushDown(rt , r - l + 1);    
  52.     int m = (l + r) >> 1;    
  53.     LL ret = 0;    
  54.     if (L <= m) ret += query(L , R , lson);    
  55.     if (m < R) ret += query(L , R , rson);    
  56.     return ret;    
  57. }    
  58. int main() {    
  59.     int N , Q;    
  60.     scanf("%d%d",&N,&Q);    
  61.     build(root);    
  62.     while (Q --) {    
  63.         char op[2];    
  64.         int a , b , c;    
  65.         scanf("%s",op);    
  66.         if (op[0] == 'Q') {    
  67.             scanf("%d%d",&a,&b);    
  68.             printf("%lld\n",query(a , b ,root));    
  69.         } else {    
  70.             scanf("%d%d%d",&a,&b,&c);    
  71.             update(a , b , c , root);    
  72.         }    
  73.     }    
  74.     return 0;    
  75. }    


模板3:

多维空间的动态查询



三:练习题目

下面是hh线段树代码,典型练习哇~

在代码前先介绍一些我的线段树风格:

  • maxn是题目给的最大区间,而节点数要开4倍,确切的来说节点数要开大于maxn的最小2x的两倍
  • lson和rson分辨表示结点的左儿子和右儿子,由于每次传参数的时候都固定是这几个变量,所以可以用预定于比较方便的表示
  • 以前的写法是另外开两个个数组记录每个结点所表示的区间,其实这个区间不必保存,一边算一边传下去就行,只需要写函数的时候多两个参数,结合lson和rson的预定义可以很方便
  • PushUP(int rt)是把当前结点的信息更新到父结点
  • PushDown(int rt)是把当前结点的信息更新给儿子结点
  • rt表示当前子树的根(root),也就是当前所在的结点

整理这些题目后我觉得线段树的题目整体上可以分成以下四个部分:



单点更新:最最基础的线段树,只更新叶子节点,然后把信息用PushUP(int r)这个函数更新上来


  • hdu1166 敌兵布阵
  • 题意:O(-1)
  • 思路:O(-1)
    线段树功能:update:单点增减 query:区间求和

code:

[cpp]  view plain  copy
 print

你可能感兴趣的:(c++)