线段树(SegmentTree)

线段树

简介

线段树可以解决大部分区间上面的修改以及查询的问题:单点修改、单点查询、区间修改、区间查询。

参考: 线段树:从没入门到入门

线段树的结构

image

构造(从1开始编号)

int a[n];	// 原数组
int sum[n*4];	// 开四倍n的大小
void push(int u){	// 由左右儿子更新父节点
    sum[u] = sum[u*2] + sum[u*2 + 1];
}
void build(int l, int r, int u){	// (子)树的左边界、右边界、根节点
    if(l == r) sum[u] = a[l];
    else{
        int mid = (l + r) / 2;
        build(l, mid, u*2);	// 递归建立左子树
        build(mid + 1, r, u*2 + 1);	// 递归建立右子树
        push(u);	// 更新子树根节点的值
    }
}

线段树的相关操作

  • 单点修改
void update(int a, int l, int r, int u, int v){	//要修改的点、子树的左边界、子树的右边界、子树的根节点,要增加的值
    if(l == r){
        a[u] += v;	//修改点(原数组)
        sum[u] += v;	//修改和
    }
    else{	// 折半查找
        int mid = (l + r) / 2;
        if(a <= mid) update(a, l, mid, u*2, v);	//递归查找左子树
        else update(a, mid + 1, r, u*2 + 1, v);	//递归查找右子树
        push(u);	// 更新子树根节点的值
    }
}
  • 区间修改
void down(int l, int r, int u){	// 下传lazy标记
    if(tag[u]){	// 如果该节点有lazy标记
        int mid = (l + r) / 2;
        tag[u*2] += tag[u];	//下传给左子树
        tag[u*2 + 1] += tag[u];	//下传给右子树
        sum[u*2] += tag[u] * (mid - l + 1);	//修改左子树和,因为该节点下面有(mid-l+1)个节点,每个节点+tag[u]
        sum[u*2 + 1] += tag[u] * (r - mid);	//修改右子树和,因为该节点下面有(r-mid)个节点,每个节点+tag[u]
        tag[u] = 0;	//该节点的lazy标记已传给左右儿子节点
    }
}
void update(int L, int R, int l, int r, int u, int v){	//修改区间的左端点、右端点,线段树的左边界、右边界,子树的根节点,要加的值
    if(l >= L && r <= R){	// 当修改区间完全覆盖当前子树的区间,把当前子树的区间和修改,并修改该节点的lazy标记值
        tag[u] += v;//修改点
        sum[u] += v * (r - l + 1); //修改和,因为该节点下面有(r-l+1)个节点,每个节点+v
    }
    else{
        down(l, r, u);	// 下传lazy标记
        int mid = (l + r) / 2;
        if(L <= mid) update(L, R, l, mid, u*2, v);	// 递归修改左子树
        if(mid < R) update(L, R, mid + 1, r, u*2 + 1, v);	// 递归修改右子树
        push(o);	// 更新子树根节点的值
    }
}
  • 单点查询
int tag[n*4], sum[n*4], ans;
void down(int l, int r, int u){	// 下传lazy标记
    if(tag[u]){	// 如果该节点有lazy标记
        int mid = (l + r) / 2;
        tag[u*2] += tag[u];	//下传给左子树
        tag[u*2 + 1] += tag[u];	//下传给右子树
        sum[u*2] += tag[u] * (mid - l + 1);	//修改左子树和,因为该节点下面有(mid-l+1)个节点,每个节点+tag[u]
        sum[u*2 + 1] += tag[u] * (r - mid);	//修改右子树和,因为该节点下面有(r-mid)个节点,每个节点+tag[u]
        tag[u] = 0;	//该节点的lazy标记已传给左右儿子节点
    }
}
void query(int l,int r,int u,int a){	//线段树左边界,右边界,节点(从1开始),查找位置
    if(l == r) ans = sum[u];	// 查找到
    else{	// 折半查找
        down(l, r, u);	// 下传lazy标记,修改子节点,再往子节点找
        int mid = (l + r) / 2;
        if(a <= mid) query(l, mid, u*2, a);	// 查找左子树
        else query(mid + 1, r, u*2 + 1, a);	// 查找右子树
    }
}
  • 区间查询
int tag[n*4], sum[n*4];
void down(int l, int r, int u){	// 下传lazy标记
    if(tag[u]){	// 如果该节点有lazy标记
        int mid = (l + r) / 2;
        tag[u*2] += tag[u];	//下传给左子树
        tag[u*2 + 1] += tag[u];	//下传给右子树
        sum[u*2] += tag[u] * (mid - l + 1);	//修改左子树和,因为该节点下面有(mid-l+1)个节点,每个节点+tag[u]
        sum[u*2 + 1] += tag[u] * (r - mid);	//修改右子树和,因为该节点下面有(r-mid)个节点,每个节点+tag[u]
        tag[u] = 0;	//该节点的lazy标记已传给左右儿子节点
    }
}
int query(int L, int R, int l, int r, int u){	//查询区间左端点、右端点,子树左边界,右边界,子树根节点
    int ans = 0;
    if(l >= L && r <= R) return sum[u];	// 当查询区间完全覆盖当前子树的区间,返回子树的值
    else{
        down(l, r, u);	// 当查询区间不完全覆盖当前子树的区间,则下传lazy标记,修改子区间的和,然后再往子区间找
        int mid=(l + r) / 2;
        if(mid >= L) ans += query(L, R, l, mid, u*2);
        if(mid < R) ans += query(L, R, mid + 1, r, u*2 + 1);
        return ans;
    }
}