又是一个学习周期,今天学了线段树基础版的,早上困得一批,什么也没听进去,只能下午自己来脑补了
首先说什么是线段树,这有一篇大佬的blog https://blog.csdn.net/zearot/article/details/4829945线段树详解,
自我理解就是一个进阶版的分治过程,先把整个大区间拆分开来,弄成好过好多个小区间(当然拆到最后还剩一个节点)通过对小区间操作然后修改与他相连的节点从而改变大区间
这样的操作只处理了整个区间的一部分,并不需要去遍历整个区间,大大节省了时间开销
据说现在的基础版本比较模板化,现在我就先奉上这两个题的代码模板吧
题目链接:hdu1166 敌兵布阵
代码: 点击即得hdu1166的AC代码(线段树)
今天学了树状数组,发现敌兵布阵这道题用树状数组来做会更简单
代码: 这是HDU1166 敌兵布阵的AC代码(树状数组写的)
题目链接:hdu1754 I Hate It
代码: 这里是HDU1754 的AC代码
下面是线段树的小板板,当然板子仅供参考,并不是万能的,深入理解后适当修改即可拿去AC题目
/*储存方式
通常用的都是堆式储存法,即编号为k的节点的左儿子编号为k*2k*2k*2,右儿子编号为k*2+1,用位
运算优化一下,以上的节点编号就变成了k>>1,k>>1l1。其它储存方式请见指针储存和动态开点。通常,
每一个线段树上的节点储存的都是这几个变量:区间左边界,区间右边界,区间的答案(这里为区间
元素之和)*/
struct node{
int l,r,sum,max,lazy;
}a[N]//N为总节点数
// 更新节点k的信息
void update(int k){
a[k].sum=a[k<<1].sum+a[k<<1|1].sum;
//- 段区间的元素和等于它的子区间的元素和
a[k].max=max(a[k<<1].sum,a[1<<1|1].sum);
//- 段区间内的最大值等于左区间的最大值和右区间最大值的最大值
}
//初始化==建树
void build(int k/*当前节点的编号*/,int l/*当前区间的左边界*/,int r/*当前区间的右边界*/){
a[k].l=l,a[k].r=r;
if(l==r){//递归到叶节点
a[k].max=a[k].sum=number[l]://其中number数组为给定的初值
return;
}
int mid=(l+r)>>1;//计算左右子节点的边界
build(k<<1,l,mid);//递归到左儿子
build(k<<1|1,mid+1,r);//递归到右儿子
update(k);//记得要用左右子区间的值更新该区间的值
}
//单点修改
void change(int k/*当前节点的编号*/.int x/*要修改节点的编号*/,int y/*要把编号为x的数字修改成y*/)
if(a[k].l==a[k].r){//如果当前区间只包含一个元素,那么该元素一定就是我们要修改的。
a[k].max=a[k].sum=y;//由于该区间的sum一定等于编号为x的数字,所以直接修改sum就可以了。
return;
}
//pushdown(k);
int mid=(a[k].l+a[k].r)/2;//计算下一层子区间的左右边界
if(x<=mid)
change(k<<1,x,y);//递归到左儿子
else change(k<<1|1,x,y);//递归到右儿子
update(k);//记得更新点k的值
}
//区间修改
void changeSegment(int k,int l,int r,int x){
//当前到了编号为k的节点,要把[l,r]区间中的所有元素的值+x
if(a[k].>=l&&a[k].r<=r){//如果找到了全部元素都要被修改的区间
a[k].sum=(a[k].r-a[k].l+1)*X;//更新该区间的sum
a[k].max=x;//更新该区间的max
a[k].lazy=x;//懒惰标记叠加
return;
}
pushdown(k);
int mid=(a[k].l+a[k].r)>>1;
if(r>mid) changeSegment(k<<1|1,r,x);
if(l<=mid) changeSegment(k<<1,r,x);
update(k);
//记得更新点k的值
}
//下传标记
void pushdown(int k){//将点k的懒惰标记下传
if(a[k].lazy==0)
return ;//如果节点k已经是叶节点了,没有子节点,那么标记就不用下传,直接删除就可以了
a[k<<1].sum=(a[k<<1].r-a[k<<1].1+1)*a[k].lazy;
a[k<<1l1].sum=(a[k<<1|1].r-a[k<<1|1].l+1)*a[k].lazy;
a[k<<1].max= a[k<<1|1].max=a[k].lazy;//给k的子节点重新赋值
a[k<<1]lazy=a[k<<1|1].lazy=a[k].lazy;//下传点k的标记
a[k].lazy=0;//记得清空点k的标记
}
//单点查询
int findmax(int k){
if(a[k].l==a[k].r)
return a[k].max;
pushdown(k);
if(a[k<<1].max>a[k<<1|1].max)
return findmax(k<<1);
else
return findmax(k<<1|1);
}
//区间求和
int query(int k,int l,int r){//当前到了编号为k的节点,查询[l..r]的和
pushdown(k);
//如果当前节点被打上了懒惰标记,那么就把这个标记下传,这一句其实也可以放在下一语句的后面
if(a[k].l>=l&&a[k].r<=r)
return a[k].sum;
int mid=(a[k].l+a[k].r)>>1;
int x=0;
if(r>mid)
x+=query(k<<1|1,1,r);
if(<=mid)
x+=query(k<<1,l,r);
return x;
}
//区间查询最大值 和上一个函数类似
int query(int k,int l,int r){
pushdown(k);
if(a[k].l>=l&&a[k].r<=r)
return a[k].max;
int mid=a[k].l+a[k].r>>1;
int maxt=-1;
if(r>mid)maxt=max(maxt,query(k<<1|1,l,r));
if(l<=mid)maxt=max(maxt,query(k<<1,l,r));
return maxt;
}