※ Leetcode - Segment Tree - 307. Range Sum Query - Mutable (线段树+树状数组两种解法以及模板的常见问题解析)
2016-08-12 13:30
721 查看
1. Problem Description
Given an integer array nums, find the sum of the elements between indices i and j (i≤j), inclusive.The update(i, val) function modifies nums by updating the element at index i to val.
Example:
Given nums = [1, 3, 5]
sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8
Note:
The array is only modifiable by the update function.
You may assume the number of calls to update and sumRange function is distributed evenly.
线段树,单点更新,区间查询
2. 线段树解法
2.1线段树区间求和模板
首先是胡浩大神的单点更新,区间求和模板:1.maxn是题目给的最大区间,而节点数要开4倍,确切的来说节点数要开大于maxn的最小2x的两倍
2.lson和rson分辨表示结点的左儿子和右儿子,由于每次传参数的时候都固定是这几个变量,所以可以用预定于比较方便的表示
3.PushUP(int rt)是把当前结点的信息更新到父结点
PushDown(int rt)是把当前结点的信息更新给儿子结点
4.rt表示当前子树的根(root),也就是当前所在的结点
补充我对模板中几个常见问题的解释:
1. rt<<1|1 的意义:rt乘2后,其末尾必为0,故rt<<1|1与rt<<1+1,rt<<1^1答案相同。
2. build函数
线段树的本质是完全二叉搜索树,后序生成二叉搜索树的过程,与二分查找类似。
得到中值,递归生成左子树,右子树,然后回溯更新每个节点(pushup)
L==R相当于到达叶子。
以这个数组为例:
[1,3,5,4,2]
我们刚开始的查询范围是1~5,从根节点1开始往下走。节点内数字表示该节点在数组中的编号,和其容纳的信息范围。比如”1 [1:5]”表示该节点容纳的是下标1到5的数字的和,由于线段树是完全二叉树,我们用数组顺序存储,这个节点在数组中被编号为1.
Ps:线段树数组的起始节点下标必须是1,不能是0,原因见5.
3. update函数
与build类似,同样通过中序遍历进行更新。
4.query函数
向下递归查找,只要我们当前到达的区间,在所要查询的区间内,它即是我们想要得到的一个子区间。
这里L:R是我们要查找的区间,l:r是当前走到的区间。
if (L <= l && r <= R) { return sum[rt]; }
计算m=l和r的平均数,m在大L和大R之间时分别搜索其左子树和右子树,然后用ret保存并返回当前节点左右子树之和。
rt << 1和rt << 1|1分别为当前rt节点左子树右子树的下标。
比如我们仍然以[1,3,5,4,2]为例
我们要查询[1:4]的区间和,我们递归的整个过程,是从1号节点[1:5]开始。
查询过程:1[1:5] - > 2[1:3] -> 4[1:2] -> 5[3:3]
这时,4[1:2] 和
5[3:3]都在我们要查询的区间[1:4]内,我们返回他俩的结果给2[1:3] = 4[1:2] + 5[3:3] = 4 + 5 = 9
然后继续查询:3[4:5] -> 6[4:4] (7[5:5]不会被查询)
同样这里6[4:4] 在我们要查询的区间[1:4]内,我们返回结果给3[4:5] = 6[4:4] = 4
最后返回1[1:5] = 2[1:3] + 3[4:5] = 9 + 4 =13
4. 为什么一定要从1到n而不是从0到n-1?
我们在进行递归构造线段树时,要填满这棵完全二叉树,走的递归顺序是1 -> 2 ->
……
填充数组的顺序是rt的左子树(rt*2)然后是rt的右子树(rt*2+1),如果从0开始,那么0*2=0,0*2+1=1本来其左子树是1号节点,右子树是2号节点,但求得的却是0和1。那么构造的二叉树就会有问题。
模板代码如下:
#include <cstdio> #define lson l , m , rt << 1 #define rson m + 1 , r , rt << 1 | 1 const int maxn = 55555; int sum[maxn<<2]; void PushUP(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } void build(int l,int r,int rt) { if (l == r) { scanf("%d",&sum[rt]); return ; } int m = (l + r) >> 1; build(lson); build(rson); PushUP(rt); } void update(int p,int add,int l,int r,int rt) { if (l == r) { sum[rt] += add; return ; } int m = (l + r) >> 1; if (p <= m) update(p , add , lson); else update(p , add , rson); PushUP(rt); } int query(int L,int R,int l,int r,int rt) { if (L <= l && r <= R) { return sum[rt]; } int m = (l + r) >> 1; int ret = 0; if (L <= m) ret += query(L , R , lson); if (R > m) ret += query(L , R , rson); return ret; } int main() { int T , n; scanf("%d",&T); for (int cas = 1 ; cas <= T ; cas ++) { printf("Case %d:\n",cas); scanf("%d",&n); build(1 , n , 1); char op[10]; while (scanf("%s",op)) { if (op[0] == 'E') break; int a , b; scanf("%d%d",&a,&b); if (op[0] == 'Q') printf("%d\n",query(a , b , 1 , n , 1)); else if (op[0] == 'S') update(a , -b , 1 , n , 1); else update(a , b , 1 , n , 1); } } return 0; }
2.2 My AC code
PS:1. 给的数组是从0开始的,为了方便构造线段树,这里用一个cnt计数。
2. sum要开到len的5倍,一开始就要开辟好空间。
3. 给的函数头无法递归,调用自己重写的函数
class NumArray
{
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
public:
int cnt;
int len;
vector<int>sum;
void PushUP(int rt)
{
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
void build(int l,int r,int rt,vector<int>& nums)
{
if(l>r)
return ;
if (l == r)
{
sum[rt]=nums[cnt++];
return ;
}
int m = (l + r) >> 1;
build(lson,nums);
build(rson,nums);
PushUP(rt);
}
void update(int p,int add,int l,int r,int rt)
{
if(l>r)
return ;
if (l == r)
{
sum[rt] = add;
return ;
}
int m = (l + r) >> 1;
if (p <= m) update(p , add , lson);
else update(p , add , rson);
PushUP(rt);
}
int query(int L,int R,int l,int r,int rt)
{
if (L <= l && r <= R) { return sum[rt]; }
int m = (l + r) >> 1;
int ret = 0;
if (L <= m) ret += query(L , R , lson);
if (R > m) ret += query(L , R , rson);
return ret;
}
//function
NumArray(vector<int> &nums)
{
len=nums.size();
for(int i=0; i<len*5; i++)
sum.push_back(0);
cnt=0;
build(1,len,1,nums);
}
void update(int i, int val)
{
update(i+1,val,1,len,1);
}
int sumRange(int i, int j)
{
return query(i+1,j+1,1,len,1);
}
};
3.树状数组解法
3.1 树状数组区间求和模板(HDU 1166 敌兵布阵)
题意大致是先输入num个数字构造树状数组,然后有三种查询。Add 将第a个元素增加b
Sub 将第a个元素减少b
Q a到b区间和
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAX=50000+10; int c[MAX]; int N; string op; int lowbit(int x) { return x&(-x); } //查询【1,x】区间和 int query(int x) { int s=0; while(x>0) { s+=c[x]; x-=lowbit(x); } return s; } //单点更新,对节点x加data void update(int x,int data) { while(x<=N) { c[x]+=data; x+=lowbit(x); } } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int T; scanf("%d",&T); int tcase=1; while(T--) { scanf("%d",&N); memset(c,0,sizeof(c)); for(int i=1;i<=N;i++) { int val; scanf("%d",&val); update(i,val); } printf("Case %d:\n",tcase++); while(cin>>op&&op!="End") { int a,b; scanf("%d%d",&a,&b); if(op=="Query") printf("%d\n",query(b)-query(a-1)); if(op=="Add") update(a,b); if(op=="Sub") update(a,-b); } } return 0; }
3.2 My AC code
Ps:1. 因为这里需要更新值,而不是在原有的值上加,必须再开一个数组保存原有值。每次update后必须修改回来!
2. Sum开到n即可
3. 注意索引从0还是从1开始的问题。
class NumArray { private: int len; vector<int>sum; vector<int>tmp; public: int lowbit(int x) { return x&(-x); } //查询【1,x】区间和 int query(int x) { int s=0; while(x>0) { s+=sum[x]; x-=lowbit(x); } return s; } //function //单点更新,对节点i加val void update(int i, int val) { //为了依照题意把第i个变成val int ti=i+1; int add=val-tmp[ti]; //这里一定要改回来! tmp[ti]=val; while(ti<=len) { sum[ti]+=add; ti+=lowbit(ti); } } NumArray(vector<int> &nums) { len=nums.size(); for(int i=0; i<=len; i++) { sum.push_back(0); tmp.push_back(0); } for(int i=0; i<len; i++) { update(i,nums[i]); tmp[i+1]=nums[i]; } } int sumRange(int i, int j) { i++,j++; return query(j)-query(i-1); } };
4.线段树与树状数组的区别
1.外观上树状数组比较简洁,常数复杂度低,空间复杂度低。2.可以用树状数组解决的问题,线段树均可解决,但可以用线段树解决的,树状数组不一定可以解决。
3.常见的三种查询方法实现方式(给a加b,查询a到b的区间):
线段树 | 树状数组 | |
单点更新,区间查询(HDU 1166) | update(a , b , 1 , n , 1) query(a , b , 1 , n , 1) | update(a,b) query(b)-query(a-1) |
区间更新,单点查询 (HDU 1556) | Push down懒惰标记+递归更新 | update(b,c); update(a-1,-c); query(j) |
区间更新,区间查询(POJ 3468) | Push down懒惰标记+递归更新 | 两树状数组,分别表示修改前、修改后,分别加一次减一次完成更新(4次)。加一次减一次完成查询。 |
时间复杂度 | logn | logn |
空间复杂度 | 4*n | n |
相关文章推荐
- (LeetCode 307) Range Sum Query - Mutable(Segment Tree)
- leetcode: Segment Tree:Range Sum Query - Mutable(307)
- [leetcode] 307. Range Sum Query - Mutable 解题报告
- [Leetcode] 307. Range Sum Query - Mutable 解题报告
- Leetcode 307. Range Sum Query - Mutable (Python)
- leetcode_307.Range Sum Query - Mutable ? 待解决
- [LeetCode] 307. Range Sum Query - Mutable 解题思路
- leetcode 307. Range Sum Query - Mutable
- leetcode 307. Range Sum Query - Mutable(树状数组)
- leetcode 307. Range Sum Query - Mutable
- Leetcode 303. Range Sum Query - Immutable & 307. Range Sum Query - Mutable
- [leetcode] 307. Range Sum Query - Mutable
- leetcode 307. Range Sum Query - Mutable 树状数组的一个应用
- LeetCode 307. Range Sum Query - Mutable
- LeetCode - 307. Range Sum Query - Mutable
- LeetCode 307. Range Sum Query - Mutable
- LeetCode 307. Range Sum Query - Mutable(区间之和)
- leetcode 307. Range Sum Query - Mutable
- leetcode 307. Range Sum Query - Mutable
- LeetCode *** 307. Range Sum Query - Mutable (Binary Indexed Trees)