线段树
1 建树
以求区间最大值为例
a数组为构造线段树的数组
root 为线段树的树根
star 为数组起始下表
end 为数组终点下表
开始 root = 1,star = 1,end = n;
void build(int root,int star,int end)
{
if(star==end)
{
int k;
scanf("%d",&k);
stu[root] = k;
return ;
}
int mid = (star+end)/2;
build(root*2,star,mid);
build(root*2+1,mid+1,end);
stu[root]=max(stu[2*root],stu[2*root+1]);
}
2 单节点更新
root 为线段树的树根
star 为数组起始下表
end为数组终点下表
i 为数组中要更新的位置
add 为这个节点 要更新的值;
void updet(int root,int star,int end,int i,int add)
{
if(star==end&&star==i)
{
stu[root]=add;
return ;
}
int mid=(star+end)/2;
int t1;
if(i<=mid) //和二分查找差不多;
updet(2*root,star,mid,i,add);
else
updet(2*root+1,mid+1,end,i,add);
stu[root] = max(stu[2*root],stu[2*root+1]);
}
3 查询线段树
root 为线段树树根
star 为数组起始下表
end 为数组终点下表
i 为要查询的区间起始下表
j 为要查询的区间终点下表
这个另一道题,查询区间和的,记住只是查询,不能改变原建树的内容;
而下面这个写的也不好,可能会出错,runtime erorr(栈溢出)
int Query(int root,int star,int end,int i,int j) // 只查询,查询区间值;
{
if(i>end||j<star)
{
return 0;
}
if(i<=star&&end<=j)
return stu[root].sum;
int mid = (star+end)/2;
int t1 = Query(root*2,star,mid,i,j);
int t2 = Query(root*2+1,mid+1,end,i,j);
//stu[root].sum=t1+t2; //千万不要再重复赋值,本来在更新值时都算好了,
//你在更新有啥意义,还把原来的值给覆盖了;
// 直接return 找到的两者的和就行了;
return t1+t2;
}
这个是查询区间最大值的,避免了runtime erorr
int Query(int root,int star,int end,int i,int j)
{
if(i<=star&&end<=j)
return stu[root];
int mid = (star+end)/2;
int ret=0;
if(i<=mid) // 减少递归;
ret=Query(root*2,star,mid,i,j);
if(j>mid) // 减少递归;(避免 runtime erorr 栈溢出)
ret=max(ret,Query(root*2+1,mid+1,end,i,j)); //这用的挺好;
return ret;
}
4 线段树区间更新 (这个以给出一个区间,给出一个数,区间中所有值,都变为了这个数)
这个要引出新的东西,“延迟标记”,,这个标记非常懒,更新节点时能停则停,即这个区间包含在需要更新的区间内,就不会更新下面的子节点了。除非需要访问或修改它的子节点,它才会往下更新。不得不说,这样效率非常高。
struct node
{
int abadd;//延迟标记
int sum;
}stu[3*Max];
(1) 建树
初始数组里面都为1,前两个数代表的数区间,第三个数是这个区间里的所有数变为这个数;
和上面建树过程一样,唯一的区别就是把节点的所有的延迟标记都初始为0
void build(int root,int star,int end) //建线段树
{
stu[root].abadd = 0; //延迟标记初始为0;
if(star==end)
{
stu[root].sum = 1;
return ;
}
int mid = (star+end)/2;
build(2*root,star,mid);
build(2*root+1,mid+1,end);
stu[root].sum = stu[root*2].sum+stu[root*2+1].sum;
}
(2) 延迟标记传给左右子节点,自己的清零
void down_updat(int root,int star,int end) //延迟标记传给左右子节点
{
if(stu[root].abadd !=0)
{
int k=stu[root].abadd;
int mid = (star+end)/2;
stu[2*root].abadd = k;
stu[2*root+1].abadd = k;
stu[2*root].sum = (mid-star+1)*k; // 左右孩子的值 也得更新;
stu[2*root+1].sum = (end-mid)*k;
stu[root].abadd = 0; //自己的延迟标记清0
}
}
(3) 查询 区间
int Query(int root,int star,int end,int i,int j) // 只查询,查询区间值;
{
if(i>end||j<star)
{
return 0;
}
if(i<=star&&end<=j)
return stu[root].sum;
down_updat(root,star,end); //传延迟标记
int mid = (star+end)/2;
int t1 = Query(root*2+1,star,mid,i,j);
int t2 = Query(root*2+2,mid+1,end,i,j);
//stu[root].sum=t1+t2; //千万不要再重复赋值,本来在更新值时都算好了,
//你在更新有啥意义,还把原来的值给覆盖了;
// 直接return 找到的两者的和就行了;
return t1+t2;
}
(4) 更新区间值
root 线段树树根
star 数组的起始下表
end 数组的终点下表
s1 要更新的区间起始下表
e1 要更新的区间终点下表
add 区间中全部要更新为的值
下面程序 只是找到当前区间的结点,更新这个节点值,这个节点上加个延迟标记,它的子孙节点只有访问了才更新,不访问就不更新(这也是这个程序的高效性,不清楚的,看上面 “延迟标记” 定义)
当这个区间有延迟标记时,同时这个区间中的值也被更新了;只剩下向下传延迟标记;
int updat(int root,int star,int end,int s1,int e1,int add) //只更新找的区间结点值,再加上个延迟标记
{
if(s1>end||e1<star)
return stu[root].sum;
if(star>=s1&&end<=e1)
{
stu[root].abadd = add;
stu[root].sum = (end-star+1)*add;
return stu[root].sum;
}
down_updat(root,star,end); //传给左右子树延迟标记
int mid = (star+end)/2;
int t1 = updat(2*root,star,mid,s1,e1,add);
int t2 = updat(2*root+1,mid+1,end,s1,e1,add); //这不和查询一样,要更新区间值的;
return stu[root].sum = t1 + t2;
}