线段树是指如下图所示的数据结构:
image
其中,对于每个标号为n,左端点是l,右端点是r的节点有:

子树 标号 左端点 右端点
左子树 2*n l floor((l+r)/2)
右子树 2*n+1 floor((l+r)/2)+1 r

使用线段树,我们可以在log n时间内完成单点修改、区间修改、区间查询

下面是线段树的构建:
对于初始数组int arr[N],我们需要构建的线段树大小为4*N即可,即int tree[4*N],通过递归建立线段树:

#define lp 2*p
#define rp 2*p+1
void bulid(int p, int l, int r){  // 构建编号为p,左端点l,右端点r的线段树
  if(l == r){  // 叶节点,值为原始arr对应的值
    tree[p] = a[l];
    return;
  }
  int mid = (l + r)/2;
  build(lp, l, mid);  // 构建左子树
  build(rp, mid+1, r);  // 构建右子树
  tree[p] = tree[lp] + tree[rp];  // 依据左右子树更新当前节点的值
}

接下来是线段树的查找操作实现(不考虑push_back的更新):

int query(int p, int l, int r, int L, int R){  // 查询编号为p,左端点为l,右端点为r上[L,R]的和
  if(r < L || R < l) return 0;  // 当前位置与查询范围无交集
  if(L <= l && r <= R) return tree[p];  // 当前位置完全包含在查询范围内
  // 有交集但不全在范围内:分左右子树查找
  int mid = (l + r)/2;
  return query(lp, l, mid, L, R) + query(rp, mid+1, r, L, R);
}

下面是线段树的单点修改:
修改中,我们一直递归到要修改的数据对应的叶节点,对其进行修改,然后重新递归修改其父节点的值。

void single_modify(int p, int l, int r, int Pos, int val){  // 修改pos位的值为val
  if(l == r){  // 到达叶节点
    tree[Pos] = val;
    return;
  }
  
  int mid = (l+r)/2;  // 通过比较pos与mid判断修改值在左子树还是右子树
  if(Pos <= mid) single_modify(lp, l, mid, Pos, val);  // 修改左子树
  else single_modify(rp, mid+1, r, Pos, val);  // 修改右子树
  tree[p] = tree[lp] + tree[rp];  // 更新当前子树
}

下面是线段树的区间修改※
区间修改中,我们考虑这样的做法:如果修改区间l-r,我们会标记与修改尽可能少的区间,等到查询到被影响的子区间时,再把这个标记传递下去。
如以上图为例,要让2,5上所有值都加5,那么要标记的段是:9、5、3。

这时我们需要定义一个标记数组,其大小与tree大小相同,即int tag[4*N],接下来会把每一个极大区间打上标记。
为此,我们要实现update函数和push_down函数以构建modify函数,同时对查询函数query做修改。

void update(int p, int val, int l, int r{
  tag[p] += val;
  tree[p] += (r-l+1)*val;
}
void push_up(int p){  // 向上更新
  tree[p] = tree[lp] + tree[rp];
}
void push_down(int p, int l, int r){  // 将节点p的懒标记向下传递
  if(tag[p] != 0){
    int mid = (l+r)/2;
    update(lp, tag[p], l, mid);
    update(rp, tag[p], mid+1, r);  // 向左右子树传递
  }
  tag[p] = 0;  // 清除当前标记
}
void modify(int p, int l, int r, int L, int R, int val){
  // p节点编号,l节点左端点,r节点右端点,给[L, R]区间上的所有元素加上val
  if(r < L || R < l){
    return;
  }
  if(L <= l && r <= R){  // 当前区间全部在更新范围内
    update(p, val, l, r);
    return;
  }
  int mid = (l+r)/2;
  push_down(p, l, r);  // 只是有交集,懒标记必须向下传递一层
  modify(lp, l, mid, L, R, val);
  modify(rp, mid+1, r, L, R, val);
  push_up(p);
  return;
}
int query(int p, int l, int r, int L, int R){  // 查询编号为p,左端点为l,右端点为r上[L,R]的和
  if(r < L || R < l) return 0;  // 当前位置与查询范围无交集
  if(L <= l && r <= R) return tree[p];  // 当前位置完全包含在查询范围内
  // 有交集但不全在范围内:分左右子树查找
  int mid = (l + r)/2;
  push_down(p, l, r);
  return query(lp, l, mid, L, R) + query(rp, mid+1, r, L, R);
}
posted on 2026-01-19 18:49  KeyuanChen  阅读(2)  评论(0)    收藏  举报