线段树 SegTree

created by Dejavu
(不断更新中)


  • 概念

线段树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(lgN),你可以在数据结构可视(visualgo)网站中看到算法的具体搭建过程。

  • c++ 模板代码

这里的代码是对区间写入优化后的代码,加入了lazy_tag用来标记区间更新的位置

//最好不要用头文件bits/stdc++.h 因为一些编译器会不支持这个头文件

#include 
using namespace std;
#define MaxN 50005
class SegTree {
public:
    int left, right;
    int sum;
    int lazy_tag;
    int mid() { return (left + right) >> 1; }
};
SegTree tree[MaxN];
void pushUp(int rt) { tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum; }
void pushDown(int rt, int len) {
    int tag = tree[rt].lazy_tag;
    if (tag) {
        tree[rt << 1].lazy_tag += tag;
        tree[rt << 1 | 1].lazy_tag += tag;
        tree[rt << 1].sum += tag*(len - (len >> 1));
        tree[rt << 1 | 1].sum += tag*(len >> 1);
        tree[rt].lazy_tag = 0;
    }
}
void build(int rt, int left, int right) {
    tree[rt].left = left;
    tree[rt].right = right;
    tree[rt].lazy_tag = 0;
    if (left == right) { cin >> tree[rt].sum;return; }
    int mid = tree[rt].mid();
    build(rt << 1, left, mid);
    build(rt << 1 | 1, mid + 1, right);
    pushUp(rt);
}
void update(int rt, int val, int left, int right) {
    if (tree[rt].left == left && tree[rt].right == right) {
        tree[rt].lazy_tag += val;
        tree[rt].sum += val*(right - left + 1);
        return;
    }
    if (tree[rt].left == tree[rt].right) return;
    pushDown(rt, tree[rt].right - tree[rt].left + 1);
    int mid = tree[rt].mid();
    if (right <= mid) update(rt << 1, val, left, right);
    else if (left > mid) update(rt << 1 | 1, val, left, right);
    else {
        update(rt << 1, val, left, mid);
        update(rt << 1 | 1, val, mid + 1, right);
    }
    pushUp(rt);
}
int query(int rt, int left, int right) {
    if (tree[rt].left == left && tree[rt].right == right) return tree[rt].sum;
    pushDown(rt, tree[rt].right - tree[rt].left + 1);
    int mid = tree[rt].mid();
    int sum(0);
    if (right <= mid) sum += query(rt << 1, left, right);
    else if (left > mid) sum += query(rt << 1 | 1, left, right);
    else {
        sum += query(rt << 1, left, mid);
        sum += query(rt << 1 | 1, mid + 1, right);
    }
    return sum;
}
int main() {
    ios::sync_with_stdio(false);
    int n;cin >> n;
    build(1, 1, n);
    char cmd;
    int in1, in2, val;
    while (cin >> cmd, cmd != 'E') {
        if (cmd == 'Q') { cin >> in1 >> in2;cout << query(1, in1, in2) << endl; }
        if (cmd == 'A') { cin >> in1 >> in2 >> val;update(1, val, in1, in2); }
    }
}

例题

  • 单点更新
    最最基础的线段树,只更新叶子节点,然后把信息用pushUP(int rt)这个函数更新上来
  • hdu1166 敌兵布阵
  • hdu1754 I Hate It
  • hdu1394 Minimum Inversion Number
  • hdu2795 Billboard
  • poj2828 Buy Tickets
  • poj2886 Who Gets the Most Candies?
  • 成段更新
    需要用到延迟标记(或者说懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候
  • hdu1698 Just a Hook
  • poj3468 A Simple Problem with Integers
  • poj2528 Mayor’s posters
  • poj3225 Help with Intervals
  • poj1436 Horizontally Visible Segments
  • 区间合并
    这类题目会询问区间中满足条件的连续最长区间,所以pushUp的时候需要对左右儿子的区间进行合并
  • poj2991 Crane
  • hdu3308 LCIS
  • hdu3397 Sequence operation
  • hdu2871 Memory Control
  • hdu1540 Tunnel Warfare
  • CF46-D Parking Lot
  • 扫描线
    这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去
    最典型的就是矩形面积并,周长并等题
  • hdu1542 Atlantis
  • hdu1828 Picture
  • hdu3265 Posters
  • hdu3642 Get The Treasury
  • poj2482 Stars in Your Window
  • poj2464 Brownie Points II
  • hdu3255 Farming
  • ural1707 Hypnotoad’s Secret
  • uva11983 Weird Advertisement

题解和代码性能分析

  • 单点更新
    • hdu1166 敌兵布阵
      求区间和
//c++ 用类似c的方法写
#include 
#include 
#define MaxN 50000
int t[MaxN * 4];
inline void pushUp(int rt) { t[rt] = t[rt << 1] + t[rt << 1 | 1]; }
void build(int rt,int l,int r) {
    if (l == r) {scanf("%d",&t[rt]);return;}
    int m = (l + r) >> 1;
    build(rt << 1, l, m);
    build(rt << 1 | 1, m + 1, r);
    pushUp(rt);
}
void update(int rt, int l, int r, int p, int v) {
    if (l == r) { t[rt] += v;return; }
    int m = (l + r) >> 1;
    if (p <= m) update(rt << 1, l, m, p, v);
    else update(rt << 1 | 1, m + 1, r, p, v);
    pushUp(rt);
}
int query(int rt,int l,int r, int _l, int _r) {
    if (_l <= l && r <= _r) return t[rt];
    int m = (l + r) >> 1;
    int sum(0);
    if (_r <= m) sum += query(rt << 1, l, m, _l, _r);
    else if (_l > m) sum += query(rt << 1 | 1, m + 1, r, _l, _r);
    else {
        sum += query(rt << 1, l, m, _l, _r);
        sum += query(rt << 1 | 1, m + 1, r, _l, _r);
    }
    return sum;
}
void main() {
    int n, tn;scanf("%d",&n);tn = n;
    while (tn--) {
        int num;scanf("%d", &num);
        build(1, 1, num);
        printf("Case %d:\n", n - tn);
        char cmd[10];
        int in1, in2;
        while (scanf("%s", &cmd)) {
            if (cmd[0] == 'E') break;
            scanf("%d%d", &in1, &in2);
            if (cmd[0] == 'A') update(1, 1, num, in1, in2);
            else if (cmd[0] == 'S') update(1, 1, num, in1, -in2);
            else printf("%d\n", query(1, 1, num, in1, in2));
            memset(cmd, '\0', sizeof(cmd));
        }
    }
}
上面代码在hdu运行的结果
//更改query函数
int sum(0);
void query(int rt, int l, int r, int _l, int _r) {
    if (_l <= l && r <= _r) {sum += t[rt];return;}
    int m = (l + r) >> 1;
    if (_r <= m) query(rt << 1, l, m, _l, _r);
    else if (_l > m) query(rt << 1 | 1, m + 1, r, _l, _r);
    else {
        query(rt << 1, l, m, _l, _r);
        query(rt << 1 | 1, m + 1, r, _l, _r);
    }
}
更改query函数将sum作为全局变量
//c++ 用c写法下,全宏定义
#include 
#include 
#define MaxN 50000
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define mid int m=(l+r)>>1
#define base int rt,int l,int r
#define we_base 1,1,num
int t[MaxN*4];
inline void pushUp(int rt) { t[rt] = t[rt << 1] + t[rt << 1 | 1]; }
void build(base) {
    if (l == r) { scanf("%d", &t[rt]);return; }
    mid;
    build(lson);
    build(rson);
    pushUp(rt);
}
void update(base, int p, int v) {
    if (l == r) { t[rt] += v;return; }
    mid;
    if (p <= m) update(lson, p, v);
    else update(rson, p, v);
    pushUp(rt);
}
int query(base, int _l, int _r) {
    if (_l <= l && r <= _r) return t[rt];
    mid;
    int sum(0);
    if (_r <= m) sum += query(lson, _l, _r);
    else if (_l > m) sum += query(rson, _l, _r);
    else {
        sum += query(lson, _l, _r);
        sum += query(rson, _l, _r);
    }
    return sum;
}
void main() {
    int n, tn;scanf("%d", &n);tn = n;
    while (tn--) {
        int num;scanf("%d", &num);
        build(we_base);
        printf("Case %d:\n", n - tn);
        char cmd[10];
        int in1, in2;
        while (scanf("%s", &cmd)) {
            if (cmd[0] == 'E') break;
            scanf("%d%d", &in1, &in2);
            if (cmd[0] == 'A') update(we_base, in1, in2);
            else if (cmd[0] == 'S') update(we_base, in1, -in2);
            else printf("%d\n", query(we_base, in1, in2));
            memset(cmd, '\0', sizeof(cmd));
        }
    }
}
c写法全宏定义

可见使用宏定义的算法运行速度比普通写法稍快一些,而且写代码的速度将会比普通写法快



//用c++的形式写
#include 
#include 
using namespace std;
#define MaxN 50000
int t[MaxN * 4];
inline void pushUp(int rt) { t[rt] = t[rt << 1] + t[rt << 1 | 1]; }
void build(int rt,int l,int r) {
    if (l == r) { cin >> t[rt];return; }
    int m = (l + r) >> 1;
    build(rt << 1, l, m);
    build(rt << 1 | 1, m + 1, r);
    pushUp(rt);
}
void update(int rt, int l, int r, int p, int v) {
    if (l == r) { t[rt] += v;return; }
    int m = (l + r) >> 1;
    if (p <= m) update(rt << 1, l, m, p, v);
    else update(rt << 1 | 1, m + 1, r, p, v);
    pushUp(rt);
}
int query(int rt,int l,int r, int _l, int _r) {
    if (_l <= l && r <= _r) return t[rt];
    int m = (l + r) >> 1;
    int sum(0);
    if (_r <= m) sum += query(rt << 1, l, m, _l, _r);
    else if (_l > m) sum += query(rt << 1 | 1, m + 1, r, _l, _r);
    else {
        sum += query(rt << 1, l, m, _l, _r);
        sum += query(rt << 1 | 1, m + 1, r, _l, _r);
    }
    return sum;
}
void main() {
    ios::sync_with_stdio(false);
    int n, tn;cin >> n;tn = n;
    while (tn--) {
        int num;cin >> num;
        build(1, 1, num);
        cout << "Case " << n - tn <<":" << endl;
        string cmd;
        int in1, in2;
        while (cin >> cmd, cmd != "End") {
            cin>> in1 >> in2;
            if (cmd == "Add") update(1, 1, num, in1, in2);
            else if (cmd == "Sub") update(1, 1, num, in1, -in2);
            else cout << query(1, 1, num, in1, in2) << endl;
        }
    }
}

ios::sync_with_stdio(false)

ios::sync_with_stdio(true)

这里的1000ms并不是真正的运行时间而是超时后就停止运行了,就是说当设置为true时,运行时间是远远超过设置为false时的时间的, sync_with_stdio(bool)这个语句是询问是否与stdio库同步,同步过程会耗费很多时间

//c++ 下全宏定义写法
#include 
#include 
using namespace std;
#define MaxN 50000
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define mid int m=(l+r)>>1
#define base int rt,int l,int r
#define we_base 1,1,num
int t[MaxN*4];
inline void pushUp(int rt) { t[rt] = t[rt << 1] + t[rt << 1 | 1]; }
void build(base) {
    if (l == r) { cin >> t[rt];return; }
    mid;
    build(lson);
    build(rson);
    pushUp(rt);
}
void update(base, int p, int v) {
    if (l == r) { t[rt] += v;return; }
    mid;
    if (p <= m) update(lson, p, v);
    else update(rson, p, v);
    pushUp(rt);
}
int query(base, int _l, int _r) {
    if (_l <= l && r <= _r) return t[rt];
    mid;
    int sum(0);
    if (_r <= m) sum += query(lson, _l, _r);
    else if (_l > m) sum += query(rson, _l, _r);
    else {
        sum += query(lson, _l, _r);
        sum += query(rson, _l, _r);
    }
    return sum;
}
void main() {
    ios::sync_with_stdio(false);
    int n, tn;cin >> n;tn = n;
    while (tn--) {
        int num;cin >> num;
        build(we_base);
        cout << "Case " << n - tn << ":" << endl;
        string cmd;
        int in1, in2;
        while (cin >> cmd, cmd != "End") {
            cin >> in1 >> in2;
            if (cmd == "Add") update(we_base, in1, in2);
            else if (cmd == "Sub") update(we_base, in1, -in2);
            else cout << query(we_base, in1, in2) << endl;
        }
    }
}
c++ 下全宏定义写法

然而使用c++下的头文件和空间名,似乎用宏定义效果并不好

代码运行各项性能对比

c写法


c++下普通c的写法

更改query函数将sum作为全局变量

c写法全宏定义

c++写法


ios::sync_with_stdio(false)

ios::sync_with_stdio(true)

c++ 下全宏定义写法

用c++的输入输出流在性能上会比c的输入输出流慢一些,即使加了去同步语句,所以acm的代码最好采用c的输入输出流

  • hdu1754 I Hate It
    求区间最大值
#include 
#define s scanf
#define p printf
#define base int rt,int l,int r
#define we_base 1,1,num
#define max(v1,v2) v1>v2?v1:v2
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define mid int m=(l+r)>>1
#define MaxN 200000
int t[MaxN * 4];
inline void pushUp(int rt) { t[rt] = max(t[rt << 1], t[rt << 1 | 1]); }
void build(base) {
    if (l == r) s("%d", &t[rt]);
    else {
        mid;
        build(lson);
        build(rson);
        pushUp(rt);
    }
}
void update(base, int pos, int v) {
    if (l == r) t[rt] = v;
    else {
        mid;
        if (pos <= m) update(lson, pos, v);
        else update(rson, pos, v);
        pushUp(rt);
    }
}
int query(base, int _l, int _r) {
    if (_l <= l&&_r >= r) return t[rt];
    mid;
    if (_r <= m) return query(lson, _l, _r);
    else if (_l > m) return query(rson, _l, _r);
    else {
        int t1 = query(lson, _l, m);
        int t2 = query(rson, m + 1, _r);
        return max(t1, t2);
    }
    }
}
void main() {
    int num, m;
    while (s("%d%d", &num, &m) != EOF) {
        build(we_base);
        char cmd;
        int in1, in2;
        while (m--) {
            s("\n%c%d%d", &cmd, &in1, &in2);
            if (cmd == 'U') update(we_base, in1, in2);
            else p("%d\n", query(we_base, in1, in2));
        }
    }
}

你可能感兴趣的:(线段树 SegTree)