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); }
}
}
例题
-
单点更新
最最基础的线段树,只更新叶子节点,然后把信息用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 敌兵布阵
求区间和
-
hdu1166 敌兵布阵
//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));
}
}
}
//更改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);
}
}
//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++的形式写
#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;
}
}
}
这里的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的输入输出流慢一些,即使加了去同步语句,所以acm的代码最好采用c的输入输出流
-
hdu1754 I Hate It
求区间最大值
#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));
}
}
}