载自:NotOnlySuccess的博客
【完全版】线段树
很早前写的那篇线段树专辑至今一直是本博客阅读点击量最大的一片文章,当时觉得挺自豪的,还去pku打广告,但是现在我自己都不太好意思去看那篇文章了,觉得当时的代码风格实在是太丑了,很多线段树的初学者可能就是看着这篇文章来练习的,如果不小心被我培养出了这么糟糕的风格,实在是过意不去,正好过几天又要给集训队讲解线段树,所以决定把这些题目重新写一遍,顺便把近年我接触到的一些新题更新上去~;并且学习了splay等更高级的数据结构后对线段树的体会有更深了一层,线段树的写法也就比以前飘逸,简洁且方便多了.
在代码前先介绍一些我的线段树风格:
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:区间求和
1 #include <cstdio> 2 3 #define lson l , m , rt << 1 4 #define rson m + 1 , r , rt << 1 | 1 5 const int maxn = 55555; 6 int sum[maxn<<2]; 7 void PushUP(int rt) { 8 sum[rt] = sum[rt<<1] + sum[rt<<1|1]; 9 } 10 void build(int l,int r,int rt) { 11 if (l == r) { 12 scanf("%d",&sum[rt]); 13 return ; 14 } 15 int m = (l + r) >> 1; 16 build(lson); 17 build(rson); 18 PushUP(rt); 19 } 20 void update(int p,int add,int l,int r,int rt) { 21 if (l == r) { 22 sum[rt] += add; 23 return ; 24 } 25 int m = (l + r) >> 1; 26 if (p <= m) update(p , add , lson); 27 else update(p , add , rson); 28 PushUP(rt); 29 } 30 int query(int L,int R,int l,int r,int rt) { 31 if (L <= l && r <= R) { 32 return sum[rt]; 33 } 34 int m = (l + r) >> 1; 35 int ret = 0; 36 if (L <= m) ret += query(L , R , lson); 37 if (R > m) ret += query(L , R , rson); 38 return ret; 39 } 40 int main() { 41 int T , n; 42 scanf("%d",&T); 43 for (int cas = 1 ; cas <= T ; cas ++) { 44 printf("Case %d:\n",cas); 45 scanf("%d",&n); 46 build(1 , n , 1); 47 char op[10]; 48 while (scanf("%s",op)) { 49 if (op[0] == 'E') break; 50 int a , b; 51 scanf("%d%d",&a,&b); 52 if (op[0] == 'Q') printf("%d\n",query(a , b , 1 , n , 1)); 53 else if (op[0] == 'S') update(a , -b , 1 , n , 1); 54 else update(a , b , 1 , n , 1); 55 } 56 } 57 return 0; 58 }