线段树
线段树 vs 树状数组
- 代码长度: 树状数组段
- 可扩展性:线段树强, 二树状数组仅局限于和的处理
- 思维难度:线段树简单 比如 区查区改 树状数组还要打开多项式搞
延迟标记:为了处理当修改区间是\([1,n]\)时所有节点都要被修改一遍的情况
如果修改区间覆盖当前区间,那么这颗子树之内所有节点都要修改,干脆在根打个标记,延迟更新
等到出现 1.修改区间未覆盖当前区间(不能省) 2.需要查询 两种情况时 下传标记
例1
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std ;
typedef long long ll ;
const int N = 100010 ;
int n, q ;
int a[N] ;
struct node {
int l, r ; ll v ;
int lt ; // lazy tag
#define ls(x) tr[x].l
#define rs(x) tr[x].r
#define v(x) tr[x].v
#define lt(x) tr[x].lt
} tr[N * 4] ;
void build(int x, int l, int r) {
ls(x) = l, rs(x) = r ; lt(x) = 0 ;
if (l == r) {
v(x) = a[l] ;
return ;
}
int mid = (l + r) >> 1 ;
build(x * 2, l, mid) ;
build(x * 2 + 1, mid + 1, r) ;
v(x) = v(x * 2) + v(x * 2 + 1) ;
}
int spread(int x) {
if (lt(x) != 0) {
lt(x * 2) += lt(x) ;
lt(x * 2 + 1) += lt(x) ;
v(x * 2) += lt(x) * (rs(x * 2) - ls(x * 2) + 1) ;
v(x * 2 + 1) += lt(x) * (rs(x * 2 + 1) - ls(x * 2 + 1) + 1) ;
lt(x) = 0 ;
}
}
void modify(int x, int l, int r, int c) {
if (l <= ls(x) && rs(x) <= r) {
v(x) += c * (rs(x) - ls(x) + 1) ;
lt(x) += c ;
return ;
}
spread(x) ;
int mid = (ls(x) + rs(x)) >> 1 ;
if (l <= mid) modify(x * 2, l, r, c) ;
if (mid + 1 <= r) modify(x * 2 + 1, l, r, c) ;
v(x) = v(x * 2) + v(x * 2 + 1) ;
}
ll query(int x, int l, int r) {
spread(x) ;
if (l <= ls(x) && rs(x) <= r) return v(x) ;
ll ans = 0 ; int mid = (ls(x) + rs(x)) >> 1 ;
if (l <= mid) ans += query(x * 2, l, r) ;
if (mid + 1 <= r) ans += query(x * 2 + 1, l, r) ;
return ans ;
}
char get() {
char t = getchar() ;
while (!isalpha(t)) t = getchar() ;
return t ;
}
int main() {
scanf("%d%d", &n, &q) ;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]) ;
build(1, 1, n) ;
while (q--) {
char op = get() ;
if (op == 'C') {
int l, r, c ; scanf("%d%d%d", &l, &r, &c) ;
modify(1, l, r, c) ;
} else {
int l, r ; scanf("%d%d", &l, &r) ;
printf("%lld\n", query(1, l, r)) ;
}
}
}
\({\color{black}\colorbox{yellow}{线段树的核心:维护满足有能够区间合并性质的数据}}\)
例2
首先:最大字段和这个数据满足区间合并性质吗?
加入现在有两段区间\(A\)和\(B\),我们要怎么得到他们的合并后的区间的答案?
一共有几种情况?类似归并,一共三种
- 答案仅在\(A\)区间
- 答案仅在\(B\)区间
- 答案横跨\(A、B\)两区间
对于\(1、2\)两种其实就是递归
第 \(3\) 个怎么维护? 发现结构一定是\(A\)的末尾拼上\(B\)的开头
维护一个区间从左端点开始(必须取)最大的字段和 \(lmax\) 和 以右端点结束(必须取)最大的字段和 \(rmax\)
\(lmax\) 维护手段:只有两种可能
- 左儿子的 \(lmax\)
- 左儿子全部拼上右儿子的 \(lmax\)
\(rmax\)维护同理
查询的时候结构用struct存两段再汇总答案
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std ;
typedef long long ll ;
const int N = 1000010 ;
int n, q ;
int a[N] ;
struct node {
int l, r, sum, lmax, rmax, v ;
// sum : 区间的和
// lmax : 紧靠左端的最大字段和
// rmax : 紧靠右端的最大字段和
// v : 区间最大字段和
#define l(x) tr[x].l
#define r(x) tr[x].r
#define sum(x) tr[x].sum
#define lmax(x) tr[x].lmax
#define rmax(x) tr[x].rmax
#define v(x) tr[x].v
} tr[N * 4] ;
void pushup(int x) {
sum(x) = sum(x * 2) + sum(x * 2 + 1) ;
lmax(x) = max(lmax(x * 2), sum(x * 2) + lmax(x * 2 + 1)) ;
rmax(x) = max(rmax(x * 2 + 1), sum(x * 2 + 1) + rmax(x * 2)) ;
v(x) = max(max(v(x * 2), v(x * 2 + 1)), rmax(x * 2) + lmax(x * 2 + 1)) ;
}
void build(int x, int l, int r) {
l(x) = l ; r(x) = r ;
if (l == r) {
sum(x) = lmax(x) = rmax(x) = v(x) = a[l] ;
return ;
}
int mid = (l + r) >> 1 ;
build(x * 2, l, mid) ;
build(x * 2 + 1, mid + 1, r) ;
pushup(x) ;
}
void modify(int x, int pos, int val) {
if (l(x) == pos && pos == r(x)) {
sum(x) = lmax(x) = rmax(x) = v(x) = val ;
return ;
}
int mid = (l(x) + r(x)) >> 1 ;
if (pos <= mid) modify(x * 2, pos, val) ;
if (mid + 1 <= pos) modify(x * 2 + 1, pos, val) ;
pushup(x) ;
}
node query(int x, int l, int r) {
if (l <= l(x) && r(x) <= r) return tr[x] ;
int mid = (l(x) + r(x)) >> 1 ;
// l -> mid | mid + 1 -> r
if (r <= mid) return query(x * 2, l, r) ;
else if (l >= mid + 1) return query(x * 2 + 1, l, r) ;
else {
node lft = query(x * 2, l, r), rgt = query(x * 2 + 1, l, r) ;
node res ;
res.sum = lft.sum + rgt.sum ;
res.lmax = max(lft.lmax, lft.sum + rgt.lmax) ;
res.rmax = max(rgt.rmax, rgt.sum + lft.rmax) ;
res.v = max(max(lft.v, rgt.v), lft.rmax + rgt.lmax) ;
return res ;
}
}
void output() {
puts("") ;
for (int i = 1; i <= n; i++) printf("%d ", a[i]) ;
cout << endl ;
for (int i = 1; i <= 4 * n; i++) {
if (l(i) == 0) break ;
printf("%d %d %d %d %d %d\n", l(i), r(i), sum(i), lmax(i), rmax(i), v(i)) ;
}
puts("") ;
}
int main() {
scanf("%d", &n) ;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]) ;
build(1, 1, n) ;
scanf("%d", &q) ;
// output() ;
while (q--) {
int op ; scanf("%d", &op) ;
if (op == 0) {
int x, y ; scanf("%d%d", &x, &y) ;
a[x] = y ;
modify(1, x, y) ;
// output() ;
} else {
int l, r ; scanf("%d%d", &l, &r) ;
printf("%d\n", query(1, l, r).v) ;
}
}
}
扫描线
Atlantis
从左向右 (按\(x\)从大到小排序)
左边界\(+1\) 有边界\(-1\)
对y离散化后区间就是最多\(4*n\)段
所有正区间即为占用的
注意离散后第\(i\)号点代表\([i,i+1]\)
朴素:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <cstring>
using namespace std ;
typedef long long ll ;
const int N = 1010 ;
struct node {
double x, y1, y2 ; int k ;
} ;
node put(double x, double y1, double y2, int k) {
node t ; t.x = x ; t.y1 = y1 ; t.y2 = y2 ; t.k = k ;
return t ;
}
node a[N] ;
bool cmp(node a, node b) {
return a.x < b.x ;
}
int n, M, Rnd ;
int c[N] ;
map <double, int> val ;
double tmp[N], raw[N] ;
double ans ;
void discrete() {
for (int i = 1; i <= n; i++) tmp[2 * i - 1] = a[i].y1, tmp[2 * i] = a[i].y2 ;
sort(tmp + 1, tmp + 2 * n + 1) ;
M = unique(tmp + 1, tmp + 2 * n + 1) - (tmp + 1) ;
for (int i = 1; i <= n; i++) {
val[a[i].y1] = lower_bound(tmp + 1, tmp + M + 1, a[i].y1) - tmp ;
raw[val[a[i].y1]] = a[i].y1 ;
val[a[i].y2] = lower_bound(tmp + 1, tmp + M + 1, a[i].y2) - tmp ;
raw[val[a[i].y2]] = a[i].y2 ;
}
}
void init() {
val.clear() ;
memset(raw, 0, sizeof(raw)) ;
memset(c, 0, sizeof(c)) ;
ans = 0.0 ;
}
int main() {
while (scanf("%d", &n) != EOF) {
if (n == 0) break ;
init() ;
for (int i = 1; i <= n; i++) {
double x1, x2, y1, y2 ; scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2) ;
a[2 * i - 1] = put(x1, y1, y2, 1) ;
a[2 * i] = put(x2, y1, y2, -1) ;
}
n *= 2 ;
sort(a + 1, a + n + 1, cmp) ;
discrete() ;
for (int i = 1; i <= n; i++) {
// 点i 代表 [i, i + 1]
for (int j = val[a[i].y1]; j < val[a[i].y2]; j++) c[j] += a[i].k ;
for (int j = 1; j < M; j++)
if (c[j]) ans += (a[i + 1].x - a[i].x) * (raw[j + 1] - raw[j]) ;
}
printf("Test case #%d\n", ++Rnd) ;
printf("Total explored area: %.2lf\n", ans) ;
}
}
线段树优化:维护区间的+1/-1
查询就是树根
有点奇怪,因为只查询树根,所以不需要懒惰标记
只需要从下向上传递即可
用 \(len\) 表示该节点的区间长度
如果加点的数值为正 那不用说,直接全段
如果为\(0\) 从两个儿子吸收长度
但是有疑问的事这样的代价是要遍历整颗子树 这样极端时间复杂度是否会变成\(O(N^2)\)?
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <cstring>
using namespace std ;
typedef long long ll ;
const int N = 1010 ;
struct node {
double x, y1, y2 ; int k ;
} ;
node put(double x, double y1, double y2, int k) {
node t ; t.x = x ; t.y1 = y1 ; t.y2 = y2 ; t.k = k ;
return t ;
}
node a[N] ;
bool cmp(node a, node b) {
return a.x < b.x ;
}
int n, M, Rnd ;
int c[N] ;
map <double, int> val ;
double tmp[N], raw[N] ;
double ans ;
void discrete() {
for (int i = 1; i <= n; i++) tmp[2 * i - 1] = a[i].y1, tmp[2 * i] = a[i].y2 ;
sort(tmp + 1, tmp + 2 * n + 1) ;
M = unique(tmp + 1, tmp + 2 * n + 1) - (tmp + 1) ;
for (int i = 1; i <= n; i++) {
val[a[i].y1] = lower_bound(tmp + 1, tmp + M + 1, a[i].y1) - tmp ;
raw[val[a[i].y1]] = a[i].y1 ;
val[a[i].y2] = lower_bound(tmp + 1, tmp + M + 1, a[i].y2) - tmp ;
raw[val[a[i].y2]] = a[i].y2 ;
}
}
void init() {
val.clear() ;
memset(raw, 0, sizeof(raw)) ;
memset(c, 0, sizeof(c)) ;
ans = 0.0 ;
}
struct segt {
int l, r, v, tag ; double len ;
#define l(x) tr[x].l
#define r(x) tr[x].r
#define v(x) tr[x].v
#define len(x) tr[x].len
} tr[4 * N];
void pushup(int x) {
if (v(x) > 0) {
len(x) = raw[r(x) + 1] - raw[l(x)] ;
} else if (l(x) != r(x)) {
len(x) = len(x * 2) + len(x * 2 + 1) ;
} else {
len(x) = 0;
}
}
void build(int x, int l, int r) {
l(x) = l, r(x) = r, v(x) = 0 ;
if (l == r) {
// len(x) = raw[l + 1] - raw[l] ;
return ;
}
int mid = (l + r) >> 1 ;
build (x * 2, l, mid) ;
build(x * 2 + 1, mid + 1, r) ;
pushup(x) ;
}
void modify(int x, int l, int r, int c) {
if (l <= l(x) && r(x) <= r) {
v(x) += c ;
pushup(x) ;
} else {
int mid = (l(x) + r(x)) >> 1 ;
if (l <= mid) modify(x * 2, l, r, c) ;
if (mid + 1 <= r) modify(x * 2 + 1, l, r, c) ;
pushup(x) ;
}
}
int main() {
while (scanf("%d", &n) != EOF) {
if (n == 0) break ;
init() ;
for (int i = 1; i <= n; i++) {
double x1, x2, y1, y2 ; scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2) ;
a[2 * i - 1] = put(x1, y1, y2, 1) ;
a[2 * i] = put(x2, y1, y2, -1) ;
}
n *= 2 ;
sort(a + 1, a + n + 1, cmp) ;
discrete() ;
build(1, 1, M - 1) ;
for (int i = 1; i <= n; i++) {
// 点i 代表 [i, i + 1]
modify(1, val[a[i].y1], val[a[i].y2] - 1, a[i].k) ;
ans += len(1) * (a[i + 1].x - a[i].x) ;
}
printf("Test case #%d\n", ++Rnd) ;
printf("Total explored area: %.2lf\n", ans) ;
}
}
动态开点线段树:放弃二进制的父子关系
记录左右儿子,查询时需要带上区间\([l,r]\)
可以维护线段树合并(例:两棵树的对应位置值相加,维护区间最大值)
int merge(int p, int q, int l, int r) {
if (!p) return q ;
if (!q) return p ;
if (l == r) {
dat(p) += dat(q) ;
return p ;
}
int mid = (l + r) >> 1 ;
lc(p) = merge(lc(p), lc(q), l, mid) ;
rc(p) = merge(rc(p), rc(q), mid + 1, r) ;
dat(p) = max(dat(lc(p)), dat(rc(p))) ;
return p ;
}

浙公网安备 33010602011771号