线段树
终于…解放了
经过了漫长的一个月的备战,总体来说还算可以吧
下面切入正题:
单点修改,单点查询,很简单的东西
区间查询,区间修改,线段树…
线段树,是一种基于分治思想的二叉树,它有如下性质:
1.线段树的每一个节点都代表一个区间
2.线段树的根节点是唯一一个,代表的区间是整个统计的范围
3.线段树的每一个叶子节点只代表一个数
4.对于每一个线段树的节点,左儿子代表左区间,右二子代表右区间
所以线段树一定是一个完全二叉树,所以我们可以按照父亲儿子的表示进行表示
空间
一共n个数,那么需要4n+1个空间,因为因为最后一个区间是n,那么总共的树的大小就是2n-1还要空出一行,那么就是2n-1+2n化简得,4n+1
线段树有区修,区查,单修,单查操作
建树O(n)
线段树是对序列进行维护,支持查询和修改命令,可以在一个区间之内建立一颗线段树,区间内的值就是左右儿子的最大值,线段树可以很方便的向上传递信息,然后比较大的传递上来,每一个叶子节点只保存a[i]的值
建树的思想其实就是不断地继续二分建树,然后直至叶子节点剩下一个,其实很好想
struct st
{
int l,r;
int date;
}tree[100*4];//四倍空间
void build(int p,int l,int r)//p编号,l左节点,r右节点
{
t[p].l=l;t[p].r=r;
if(l==r)//单节点一个区间
{
t[p].date=a[l];
return ;
}
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
t[p].date=max(t[p*2].date,t[p*2+1].date);
}
单点修改O(log n)
修改指令其实就是一个“1 x v”的指令,表示把a[x]修改成v
虽然这个线段树是单点修改,但是每次修改一个点,都会影响一个区间,所以我们需要递归往上更新区间的值
在线段树中,根节点是所有指令的入口,我们需要从根节点出发,不断向下二分查询,直至找到需要修改的点,然后再往上更新,每一个区间块的值
void change(int p,int x,int v)
{
if(t[p].l==t[p].r)//找到这个点了,进行更新
{
t[p].date=v;
return ;
}
int mid=(t[p].l+t[p].r)/2;
if(x<=mid) change(p*2,x,v);
else change(p*2+1,x,v);//二分递归查找x
t[p].date=max(t[p*2].date,t[p*2+1].date);//向上更新
}
区间查询O(log n)
区间查询就是一个类似于“Q l r”的指令,例如查询序列A的区间[l,r]的最大区间值,我们还是需要从根节点出发,并执行如下的过程:
1.如果这个区间[l,r]覆盖了当前区间代表点,就直接结束
2.如果这个区间与左儿子有重叠部分就递归访问左子树,二分查找
3.如果这个区间与右儿子有重叠部分就递归右子树,二分查找
int ask(int p,int l,int r)
{
if(l<=t[p].l&&r>=t[p].r) return t[p].date;//完全覆盖找到此区间
int mid=(t[p].l+t[p].r)/2;//二分查询区间
int val=-9999999;
if(l<=mid) val=max(val,ask(p*2,l,mid));
else val=max(val,ask(p*2+1,mid,r));
return val;
}
区间求和
对于区间求和的操作,就是需要递归找到这个区间,然后计算就好
int query(int x,int l,int r,int from,int to)
{
if(l>=from&&r<=to) return tree[x].sum;//找到区间
int ans=0;
if(from<=mid) ans+=query(x*2,l,mid,from,to);
if(to>mid) ans+=query(x*2+1,mid+1,r,from,to);//加上区间
return ans;
}
懒标记,就是一种类似于flag的标识,为什么叫做懒标记呢,因为我们这样做就是为了省事,在线段树向上或者向下传递中,会一步一步传递,比较浪费时间,也就是说,我们只为了一个答案,根本不需要进行一个多余的进行传递,所以我们只要在回溯前增加一个标记就好了表示“该节点曾经被修改了,但是子节点没有被修改”,然后不断地将这个求区间和的指令进行下传,递归执行指令
void pushdown(int x,int l,int r)
{
tree[x*2].add+=tree[x].add;
tree[x*2+1].add+=tree[x].add;//下传懒标记
tree[x*2].sum+=tree[x].add*(mid-l+1);
tree[x*2+1].sum+=tree[x].add*(r-mid);
}
int query(int x,int l,int r,int from,int to)
{
if(l>=from&&r<=to) return tree[x].sum;//找到区间
pushdown(x,l,r);//下传
if(from<=mid) ans+=query(x*2,l,mid,from,to);
if(to>mid) ans+=query(x*2+1,mid+1,r,from,to);//加上区间
}
如果变成区间修改,就不是加上懒标记,直接复制懒标记
对于查询最值的操作
pushup()
{
tree[x].min = min(tree[lc].min, tree[rc].min)
}
pushdown()
{
tree[lc] += tree[x].add, tree[rc] += tree[x].add
}
总的来说,线段树,就是一种实现二维区间的数据结构,支持查询修改操作