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 |