线段树的基本操作
1、特点:与普通的树不同,线段树存取的是某一个区间,它在各个节点保存一条线段。
2、节点储存方式:(结构体)
方式一:数组储存
struct Tree
{
int leftnode, rightnode; //区间的最左端和最右端
long long sum; //该节点储存区间的值
} tree[N *4]; //比常规储存的数组要大四倍(Be careful!)
方式二:链表储存(自己了解)
struct Tree
{
int Left, Right;
Tree *Leftchild , *Rightchild;
}
3、建树
思路: if(叶节点) 存值,返回;
else 递归,查找左右孩子;
最后一定要记得回溯,维护区间;
void Build_Tree ( int left , int right , int i )
{
tree[i].leftnode = left;
tree[i].rightnode = right; //存储区间最左端、最右端
if( left == right )
tree[i].sum = a[x] ; //叶子节点,存值
else
{
int mid = (tree[i].leftnode+ tree[i].rightnode)/2;
//↑↑↑可以尝试用位运算代替,速度会加快
Build_Tree ( left , mid , i/2 ); //遍历左子树
Build_Tree ( mid + 1 , right , i/2+1); //遍历右子树
tree[i].sum = ……;(回溯,对线段树进行维护)
}
}
4、查找线段树上的位置
思路: 递归查询
If(被左儿子覆盖)
查询左儿子;
Else if(被右儿子覆盖)
查询右儿子;
Else //即左右儿子都有
都查询;
int Query_Tree ( int left , int right , int i ) //Query:查找、查询
{ //left,right:查找目标的区间
if ( left<= tree[i].leftnode && right >= tree[i].rightnode )
return tree[i].sum; //当前结点的区间完全被目标区间覆盖
else
{
int mid = (tree[i].leftnode+ tree[i].rightnode)/2;
if( left > mid ) //目标区间被左儿子覆盖
return Query_Tree ( left , right , i/2+1);
else if (rifgt<= mid ) //目标区间被右儿子覆盖
return Query_Tree ( left , right , i/2 );
else //目标区间在左右都有分部
return Query_Tree ( left , right , i/2) + Query_Tree ( left , right , i/2+1 );
}
}
5、树节点值的更新与维护
思路 更新很容易,查询到位置后替换即可,重要的是维护,回溯即可
If(目标子节点)
更新;
Else
{
If(被左儿子覆盖)
查询左儿子;
If(被右儿子覆盖)
查询右儿子;
}
回溯,更新父节点值;
void Update_Tree ( int aimnode , int i ) //update更新 aimnode目标子节点
{
if(tree[i].leftnode == aimnode && tree[i].rightnode == aimnode)
//找到需要修改的叶子节点
{
tree[i].sum = ㊣ ; //更新当前结点 ,㊣为要更新的值
}
else //当前结点是非叶子结点
{
int mid = (tree[i].leftnode+ tree[i].rightnode )/2 ;
if ( aimnode <= mid ) //目标节点在左儿子中
{
Update_Tree ( aimnode , i/2 );
}
else if( aimnode > mid ) //目标节点在右儿子中
{
Update_Tree ( aimnode , i/2+1 );
}
tree[i].sum = tree[i/2].sum + tree[i/2+1].sum; //回溯
}
}
【练习】
http://codevs.cn/problem/1228/ 苹果树
http://codevs.cn/problem/1080/ 线段树练习
http://codevs.cn/problem/1217/ 借教室
https://www.luogu.org/problem/show?pid=3372 【模板】线段树 1