线段树 SegTree

created by Dejavu
(不断更新中)


  • 概念

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

  • c++ 模板代码

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

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

#include <iostream>
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); }
    }
}

例题

题解和代码性能分析

//c++ 用类似c的方法写
#include <stdio.h>
#include <string.h>
#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 <stdio.h>
#include <string.h>
#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 <iostream>
#include <string>
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 <iostream>
#include <string>
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的输入输出流

#include <cstdio>
#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));
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容