线段树初步(1)
蒟蒻终于要开始好好学线段树了……
线段树是一种二叉树形结构(二叉搜索树),属于平衡树的一种。它将线段区间组织成树形的结构,并用每个节点来表示一条线段[a,b]。每个节点的左右儿子线段分别是该线段的左半[a,(a+b)/2]和右半[(a+b)/2+1,b]区间,递归定义之后就是一棵线段树。
1.建树
既然线段树是递归建立的,那么我们就在递归到叶节点的时候把数据植入,之后递归返回的时候,将要修改的节点的值修改为其子节点的值之和即可。
void build(int l,int r,int x)//递归建树,到叶节点输入,之后递归返回修改 { if(l == r) { tree[x] = read(); return; } int mid = (l+r) >> 1; build(l,mid,x << 1); build(mid+1,r,x << 1 | 1); tree[x] = tree[x << 1] + tree[x << 1 | 1]; }
2.单点修改(增加或者删除)
因为每条节点表示一条线段……所以在单点修改的时候我们只要找到需要修改的节点所在的位置,之后当当前区间l==r的时候修改这个点就可以。如果要删除,就往里面传负数就行。
注意修改结束之后返回的时候也要……递归把其父亲和祖先的值都修改一遍。
void update(int k,int val,int l,int r,int x)//单点修改 { if(l == r) { tree[x] += val; return; } int mid = (l+r) >> 1; if(k <= mid) update(k,val,l,mid,x << 1); else update(k,val,mid+1,r,x << 1 | 1); tree[x] = tree[x << 1] + tree[x << 1 | 1]; }//k为要修改的节点编号,val为修改值,l,r为左右区间
3.区间查询
给定一段区间,求区间中所有元素之和。
当我们当前访问的区间其被完全包含于所求区间的时候,直接把这段区间和加上即可。
否则的话我们进行二分,把所求区间分割成更小的区间,之后找到能被其完全包含的区间进行累加即可。(也就是相当于我们把所求区间分割成了连续的小区间之后求和)
累加的过程同样是在递归中完成的。
int query(int kl,int kr,int l,int r,int x) { if(kl <= l && kr >= r) return tree[x]; int mid = (l+r) >> 1; int sum = 0; if(kl <= mid) sum += query(kl,kr,l,mid,x << 1); if(kr > mid) sum += query(kl,kr,mid+1,r,x << 1 | 1); return sum; } //kl为查询左端点,kr为查询右端点,l,r为当前区间作用端点,x为节点编号
会了这几个操作之后就可以做一道稍微简单一点的练习题了。
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人。
【输入格式】
第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令
【输出格式】
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
这道题为了方便我把字符串都改成了数字。
操作就三个,建树,单点修改,区间查询,上面三个操作正好够用。
#include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<iostream> #include<cstdlib> #include<queue> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') using namespace std; const int M = 1000005; typedef long long ll; int tree[M*4],k = 1,c,t,n,p,q,num; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } void build(int l,int r,int x)//递归建树,到叶节点输入,之后递归返回修改 { if(l == r) { tree[x] = read(); return; } int mid = (l+r) >> 1; build(l,mid,x << 1); build(mid+1,r,x << 1 | 1); tree[x] = tree[x << 1] + tree[x << 1 | 1]; } void update(int k,int val,int l,int r,int x)//单点修改 { if(l == r) { tree[x] += val; return; } int mid = (l+r) >> 1; if(k <= mid) update(k,val,l,mid,x << 1); else update(k,val,mid+1,r,x << 1 | 1); tree[x] = tree[x << 1] + tree[x << 1 | 1]; }//k为要修改的节点编号,val为修改值,l,r为左右区间 int query(int kl,int kr,int l,int r,int x) { if(kl <= l && kr >= r) return tree[x]; int mid = (l+r) >> 1; int sum = 0; if(kl <= mid) sum += query(kl,kr,l,mid,x << 1); if(kr > mid) sum += query(kl,kr,mid+1,r,x << 1 | 1); return sum; } //kl为查询左端点,kr为查询右端点,l,r为当前区间作用端点,x为节点编号 int main() { t = read(); while(t--) { n = read(); build(1,n,1); c = read(); rep(i,1,c) { num = read(); if(num == 4) break; p = read(),q = read(); if(num == 1) update(p,q,1,n,1); if(num == 2) update(p,-q,1,n,1); if(num == 3) printf("%d\n",query(p,q,1,n,1)); } } return 0; } /* 1 10 1 2 3 4 5 6 7 8 9 10 7 3 1 3 1 3 6 3 2 7 2 10 2 1 6 3 3 3 10 4 */