线段树--从入门到入土入门篇:没有pushdown的简单建树&查询&修改(单点+区间)
Posted Mint-hexagram
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树--从入门到入土入门篇:没有pushdown的简单建树&查询&修改(单点+区间)相关的知识,希望对你有一定的参考价值。
一、线段树的入门(一些基本的性质和建树)
struct tree
{
int l, r, sum;
}tree[maxn];
tree[i].l是节点i代表的线段的左端点,tree[i].r是节点i代表的线段的右端点,
tree[i].sum是节点i代表的线段和(线段权值之和)
性质1:节点i的权值=她的左儿子的权值+她的右儿子的权值
性质2(二叉树的性质):节点i的左儿子的编号 = i<<1 节点i的右儿子的编号 = i<<1+1
因此有:tree[i].sum = tree[2<<i].sum + tree[2<<i+1].sum;
建树: (递归建树)
inline void build(int i, int l, int r)
{
tree[i].l = l;
tree[i].r = r;
if(l == r)
{
tree[i].sum = input[l];
return;
}
int mid = (l+r) >> 1;
build(i>>1,l,mid);
build(i>>1+1,mid+1,r);
tree[i].sum = tree[i<<1].sum + tree[i<<1+1].sum;
return;
}
二、简单的线段树(无pushdown)
(1)单点修改,区间查询:
<1>区间查询:
基本思路:
1、如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
2、如果这个区间的左儿子和目标区间有交集,那么搜索左儿子
3、如果这个区间的右儿子和目标区间有交集,那么搜索右儿子
inline int search(int i,int l,int r)//l,r是要求和的区间的左右端点
{
if(tree[i].l >= l && tree.r <= r)
{
return tree[i].sum;//如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
}
if(tree[i].l < l || tree[i].r > r) return 0;//如果这个区间和目标区间毫不相干,返回0
int s = 0;
if(tree[i<<1].r >= l) s += search(i<<1,l,r);//如果这个区间的左儿子和目标区间有交集,那么搜索左儿子
if(tree[i<<1+1].l <= r) s += search(i<<1 + 1,l,r);//如果这个区间的右儿子和目标区间有交集,那么搜索右儿子
return s;
}
<2>单点修改:
基本思路:
1、先到达要修改的点
2、修改这个点
3、返回时修改所有经过的区间(因为这些点都受到这个修改的点的影响)
inline void add(int i,int dis,int k)//dis是要修改的点的编号,k是要在dis点上加的值
{
if(tree[i].l == tree[i].r)//如果是叶子节点,那么说明找到了
{
tree[i].sum += k;
return;
}
if(dis <= tree[i<<1].r) add(i<<1,dis,k);//在哪往哪跑
else add(i<<1+1,dis,k);
//这两行的意义是将修改后产生的影响传递下去(传递到整个区间中)
tree[i].sum = tree[i<<1].sum + tree[i<<1+1].sum;//返回更新
return;
}
(2)区间修改&单点查询
<1>区间修改
基本思路:
在区间查询的基础上,对原先的三个原则的第一条进行修改
1、如果这个区间被完全包括在目标区间里面,将这个区间标记k
2、如果这个区间的左儿子和目标区间有交集,那么搜索左儿子
3、如果这个区间的右儿子和目标区间有交集,那么搜索右儿子
inline void add(int i,int l, int r, int k)//i是当前搜到的点的编号,l,k是目标区间的左右端点
{
if(tree[i].l >= l && tree[i].r <= r)//如果当前区间完全在目标区间内,则进行修改(将这个区间的总值加上k)
{
tree[i].sum += k;
return;
}
if(tree[i<<1].r >= l)
{
add(i<<1,l,r,k);
}
if(tree[i<<1+1].l <= r)
{
add(i<<1+1,l,r,k);
}
//这里不能写else,因为有可能当前节点的左右儿子都在目标区间范围内,都需要被修改
}
区间修改和单点修改最大的不同就在于:
(1)单点修改将修改造成的影响传递下去的方式是在搜到目标叶节点后,在返回的过程中,将经过的每一个区间都加上修改值k
(2)而区间修改是如果当前搜到的区间在要修改的目标区间中,就把它加上修改值k
第一个的正确性是显而易见的,而第二的正确性是因为add程序保证了每一个在目标区间内的叶节点肯定都会被搜到,因此每一个应被
节点都会被搜到,因此每一个应当被修改的区间肯定就被修改了。
(注意这里肯定没有把所有的节点都处理一遍,因为搜索的过程不断地在判断找到正确的方向)
综上,完事。
<2>单点查询:
基本思路:
(1)搜索目标点
(2)搜到目标点直接返回目标点的值
inline int search(int i,int dis)
{//需要查询的点的端点(这是一个点,所以tree[目标点编号].l == tree[目标点编号].r)
if(tree[i].l==tree[i].r)
{
return tree[i].sum;
}
if(dis<=tree[i<<1].r)
{
search(i<<1,dis);
}
if(dis>=tree[i<<1+1].l)
{
search(i<<1+1,dis);
}//这里也不能写else
}
以上是关于线段树--从入门到入土入门篇:没有pushdown的简单建树&查询&修改(单点+区间)的主要内容,如果未能解决你的问题,请参考以下文章