线段树简析
线段树资料:http://dongxicheng.org/structure/segment-tree/
PS: 以HDU1166为例
线段树的使用一般分如下几个操作
- 建树
- 插入
- 查询
- 建树
建树的操作类似与构建一个二叉树,是一个递归的过程
void build(int l, int r, int step) { tree[step].left = l; tree[step].right = r; tree[step].add = 0; if (l == r) //到达叶子节点了 { tree[step].num = a[l]; return; } int mid = (l + r) >> 1; build(l, mid, L(step)); build(mid+1, r, R(step)); //其构造是自下而上(先求出其子节点的值),那么其父亲节点很明显就是其左右节点值的和 tree[step].num = tree[L(step)].num + tree[R(step)].num; }
- 插入
在建立好之后的线段树中,若要是修改一段区间的值时如:[l, r] 代码如下
void insert(int l, int r, int step, int add) { if (tree[step].left == tree[step].right) { tree[step].num += add; return; } int mid = (tree[step].left + tree[step].right) >> 1; if (mid >= l) insert(l, r, L(step), add); if (mid < r) insert(l, r, R(step), add); //由于可能其子节点有更新,我们需要在重新计算一次step这层的值 tree[step].num = tree[L(step)].num + tree[R(step)].num; }
结果正确,但是会超时,因此在这里我们添加了一个标志位,当到达某一层的时候,我们判断一下该层的标志位是否有值,若是有,则修改其左右节点的值,其他的子节点暂时不管,(其实在有些情况下可能根本就不需要用到其子节点的值)代码如下
void update(int step) //若当前节点的标志位add有值,则更新其左右节点 { tree[L(step)].num += tree[step].add * (tree[L(step)].right - tree[L(step)].left + 1); tree[L(step)].add += tree[step].add; //将标志位传给子节点 tree[R(step)].num += tree[step].add * (tree[R(step)].right - tree[R(step)].left + 1); tree[R(step)].add += tree[step].add; tree[step].add = 0; //父节点清0 }
其完整代码如下
void insert(int l, int r, int step, int add) { if (l<= tree[step].left && tree[step].right <= r) { tree[step].add += add; tree[step].num += add * (tree[step].right- tree[step].left+ 1); //add * len return; } if (tree[step].add != 0) // { update(step); } int mid = (tree[step].left + tree[step].right) >> 1; if (mid >= l) insert(l, r, L(step), add); if (mid < r) insert(l, r, R(step), add); //由于可能其子节点有更新,我们需要在重新计算一次step这层的值 tree[step].num = tree[L(step)].num + tree[R(step)].num; }
- 查询
查询的过程和建立有点相似,只是需要返回每一层的结果即可,注意:在查询的时候我们也需要判断其标志位
int query(int l, int r, int step) { int ans = 0; if (l <= tree[step].left && tree[step].right <= r) { return tree[step].num; } if (tree[step].add != 0) { update(step); } int mid = (tree[step].left + tree[step].right) >> 1; if (mid >= l) //与左节点有交集 ans += query(l, r, L(step)); if (mid < r) //与右节点有交集 ans += query(l, r, R(step)); return ans; }
附上HDU1166完整代码如下:
/* HDU1166 */ #include <iostream> using namespace std; const int N = 50005; struct node { int left, right; int num, add; }tree[4*N]; int L(int n) { return n<<1; } int R (int n) { return (n<<1) | 1; } void update(int step) //若当前节点的标志位add有值,则更新其左右节点 { tree[L(step)].num += tree[step].add * (tree[L(step)].right- tree[L(step)].left + 1); tree[L(step)].add += tree[step].add; //将标志位传给子节点 tree[R(step)].num += tree[step].add * (tree[R(step)].right - tree[R(step)].left + 1); tree[R(step)].add += tree[step].add; tree[step].add = 0; //父节点清0 } int a[N]; void build(int l, int r, int step) { tree[step].left = l; tree[step].right = r; tree[step].add = 0; if (l == r) //到达叶子节点了 { tree[step].num = a[l]; return; } int mid = (l + r) >> 1; build(l, mid, L(step)); build(mid+1, r, R(step)); //其构造是自下而上(先求出其子节点的值),那么其父亲节点很明显就是其左右节点值的和 tree[step].num = tree[L(step)].num + tree[R(step)].num; } void insert(int l, int r, int step, int add) { if (l<= tree[step].left && tree[step].right <= r) { tree[step].add += add; tree[step].num += add * (tree[step].right- tree[step].left + 1); //add * len return; } if (tree[step].add != 0) // { update(step); } int mid = (tree[step].left + tree[step].right) >> 1; if (mid >= l) insert(l, r, L(step), add); if (mid < r) insert(l, r, R(step), add); //由于可能其子节点有更新,我们需要在重新计算一次step这层的值 tree[step].num = tree[L(step)].num + tree[R(step)].num; } int query(int l, int r, int step) { int ans = 0; if (l <= tree[step].left && tree[step].right <= r) { return tree[step].num; } if (tree[step].add != 0) { update(step); } int mid = (tree[step].left + tree[step].right) >> 1; if (mid >= l) //与左节点有交集 ans += query(l, r, L(step)); if (mid < r) //与右节点有交集 ans += query(l, r, R(step)); return ans; } int main() { int t, n, i, k, l, r; char str[10]; scanf("%d", &t); for (k=1; k<=t; k++) { printf("Case %d:\n", k); scanf("%d", &n); for (i=1; i<=n; i++) scanf("%d", &a[i]); build(1, n, 1); while (scanf("%s", str) && str[0] != 'E') { scanf("%d%d", &l, &r); if (str[0] == 'Q') { printf("%d\n", query(l, r, 1)); } else { r = str[0] == 'S' ? -r : r; insert(l, l, 1, r); } } } return 0; }