- 建树和初始化
- 更新
- 查找
线段树,顾名思义,是树也是线段,但是这里的线段其实是用区间表示,而线段树也由于这个区间关系,可以用来维护区间的某些的性质,比如一个区间最值啊,和啊什么的。
建树和初始化
和大多树一样,在c语言中,树的建立可以用结构体数组来建,数组下标是指这个节点的位置,结构体里面包含了这个节点的左儿子和右儿子(树的特征),但是这个左右儿子是指这个节点包含的区间,
重点:只有左右儿子相等时(也就是这棵树的叶子)才会是实际的数据。
在结构体数组中还存放着另一个变量——用来记录区间的某些性质(另一个特征),比如sum,max,min什么的
代码:
struct tree{
int l,r;
long long sum,add;
}t[num];
void bulid(int p,int l,int r){
t[p].l=l;t[p].r=r;
if(l==r){
t[p].sum=a[l];
return;
}
int mid=l+r>>1;
bulid(p*2,l,mid);
bulid(p*2+1,mid+1,r);
t[p].sum=t[p*2].sum+t[p*2+1].sum;
}
更新
-
懒标记
这里要先介绍一个很神奇的地方,就是懒标记。在更新的时候,如果每次更新都把树从根到叶子都遍历一遍,虽然很好,但是会花时间,我们可以利用并查集优化的思想,就是搁置争议,简单来说,就是先把需要更新的数据先不动,等你要查询的时候,边查询边更新 -
更新(spread+change)
代码
void spread(int p)//p是节点的名字
{
if(t[p].add){
t[p*2].pre+=t[p].add*(t[p*2].r-t[p*2].l+1);
t[p*2+1].pre+=t[p].add*(t[p*2+1].r-t[p*2+1].l+1);
t[p*2].add+=t[p].add;
t[p*2+1].add+=t[p].add;
t[p].add=0;
}
}
void change(int p,int x,int y,int z)//x,y是区间,z是要加或减的数
{
if(x<=t[p].l && y>=t[p].r){
t[p].pre+=(long long)z*(t[p].r-t[p].l+1);
t[p].add+=z;
return;
}
spread(p);
int mid=t[p].l+t[p].r>>1;
if(x<=mid) change(p*2,x,y,z);
if(y>mid) change(p*2+1,x,y,z);
t[p].pre=t[p*2].pre+t[p*2+1].pre;
}
查询
上面讲了一部分,其实也就没剩下什么新东西了,就是dfs的基本套路。
代码
long long ask(int p,int x,int y){
if(x<=t[p].l && y>=t[p].r) return t[p].pre;
spread(p);
int mid=t[p].l+t[p].r>>1;
long long ans=0;
if(x<=mid) ans+=ask(p*2,x,y);
if(y>mid) ans+=ask(p*2+1,x,y);
return ans;
}
也就是有点二分的感觉
至于主函数部分就不贴了,就是按题写就好了。
谢谢大佬。