刚学习了线段树,找个模板题练练手;
先说什么是线段树:(以下定义来自百度百科)
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。
线段树的操作有以下几种:
1.建树;
2.查询;
3.点更新;
4.区间更新;
这个题呢很模板,一个建树,一个查询,一个点更新;三个函数完事;
没有涉及到区间更新(这个才是线段树的大头);
先说一下建树:
找区间1-10中的最小值;
1-10为1,2,3,4,5,6,7,8,9,10;
void build(int node, int left, int right, int &vel){//传参为树根, 左边界, 右边界, 所需叶节点的值; //该树与题目无关,vel值从1到10;
if(left==right){ //if左右边界相等则到达树的叶子,赋值,每次赋完值,vel自增;
segtree[node]=vel;
vel++;
return;
}
build(node*2, left, (left+right)/2, vel); // 先建左子树;
build(node*2+1, (left+right)/2+1, right, vel);//后建右子树;
segtree[node]=min(segtree[node*2],segtree[node*2+1]);//父节点存最小的数值;
}
然后是点更新:
void updata(int node, int left, int right, int ind, int add){//传参为树根,左右边界,改变的点,增加的值;
if(left==right){ //if左右边界相等,找到该点,该结点值改变;
segtree[node]+=add;
return;
}
int mid=(left+right)>>1;//相当于(left+right)/2;
if(ind<=mid) //改变的点在左边,找左子树;
updata(node*2, left, mid, ind, add);
else //在右边,找右子树;
updata(node*2+1, mid+1, right, ind, add);
segtree[node]=min(segtree[node*2],segtree[node*2+1]);//维护父结点一直是最小值;
}查询:
int query(int node, int beginn, int endd, int left, int right)//传参为树根, 已知的区间边界,所需查询的区间边界;
{
int p1, p2;
if (left > endd || right < beginn) //查询的区间不在已知区间,即找不到查询区间,返回无穷;
return INF;
if (beginn >= left && endd <= right) //查询区间比已知区间大,直接返回segtree[node];根结点;
return segtree[node];
return min(query(2 * node, beginn, (beginn + endd) / 2, left, right)
query(2 * node + 1, (beginn + endd) / 2 + 1, endd, left, right));//分别到左右找符合查询区间的min;
}
下面看一下例题 :
敌兵布阵
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.Input
第一行一个整数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条命令
Output
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
Sample Input
1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End
Sample Output
Case 1:
6
33
59
下面就上代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
#define MAX 50005
int segtree[MAX*4];
int arr[MAX];
void build(int node, int left, int right){ //建树;传参为树根, 左边界, 右边界;
if(right==left){
segtree[node]=arr[left];
return;
}
int mid=(left+right)/2;
build(node*2, left, mid);
build(node*2+1, mid+1, right);
segtree[node]=segtree[node*2]+segtree[node*2+1];
}
void add(int node, int left, int right, int ind, int x){//点更新;传参为树根,左边界, 右边界, 改变的点, 增加的值;
if(left==right){
segtree[node]+=x;
return;
}
int mid=(left+right)/2;
if(ind<=mid) add(node*2, left, mid, ind, x);
else add(node*2+1, mid+1, right, ind, x);
segtree[node]=segtree[node*2]+segtree[node*2+1];
}
int query(int node, int left, int right, int l, int r){//查询;传参为树根, 已知左边界右边界, 查询的左边界右边界;
if(l<=left && r>=right){
return segtree[node];
}
int mid=(left+right)/2;
if(r<=mid) return query(node*2, left, mid, l, r);
else if(l>mid) return query(node*2+1, mid+1, right, l, r);
else return query(node*2, left, mid, l, mid) + query(node*2+1, mid+1, right, mid+1, r);
}
int main(){
int T;
scanf("%d",&T);
int cnt=0;
while(T--){
printf("Case %d:\n",++cnt);
int N;
scanf("%d",&N);
int i;
for(i=1; i<=N; i++)
scanf("%d",&arr[i]);
build(1, 1, N);
char s[20];
while(scanf("%s",s)){
if(s[0]=='E') break;
int a, b;
scanf("%d%d",&a,&b);
if(s[0]=='A') add(1, 1, N, a, b);
else if(s[0]=='S') add(1, 1, N, a, 0-b);
else{
int sum;
sum=query(1, 1, N, a, b);
printf("%d\n",sum);
}
}
}
return 0;
}

本文介绍了线段树的基本概念及实现方式,并通过一个具体的敌兵布阵问题演示了如何运用线段树进行高效的区间查询与更新操作。
183

被折叠的 条评论
为什么被折叠?



