线段树入门之成段更新
Posted codedecision
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树入门之成段更新相关的知识,希望对你有一定的参考价值。
- 作者:zifeiy
- 标签:线段树
HDU1698 Just a Hook
- 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1698
- 线段树功能:
- update:成段更新(因为query操作只涉及一次总区间,所以可以直接输出1节点的信息)
#include <bits/stdc++.h>
using namespace std;
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
const int maxn = 100010;
int sum[maxn<<2], lazy[maxn<<2];
inline void push_up(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; }
inline void push_down(int rt, int len) {
if (lazy[rt]) { // 只有在有延迟标记(说明之前全区间覆盖过)才生效
int l_len = len-len/2, r_len = len/2;
lazy[rt<<1] = lazy[rt<<1|1] = lazy[rt];
sum[rt<<1] = l_len * lazy[rt];
sum[rt<<1|1] = r_len * lazy[rt];
lazy[rt] = 0;
}
}
void build(int l, int r, int rt) {
lazy[rt] = 0;
if (l == r) {
sum[rt] = 1;
return;
}
int m = (l + r) >> 1;
build(lson); build(rson); push_up(rt);
}
void update(int L, int R, int val, int l, int r, int rt) {
if (L <= l && r <= R) {
lazy[rt] = val;
sum[rt] = (r - l + 1) * val;
return;
}
push_down(rt, r-l+1);
int m = (l + r) >> 1;
if (L <= m) update(L, R, val, lson);
if (R > m) update(L, R, val, rson);
push_up(rt);
}
int T, n, m, a, b, c;
int main() {
scanf("%d", &T);
for (int cas = 1; cas <= T; cas ++) {
scanf("%d%d", &n, &m);
build(1, n, 1);
while (m --) {
scanf("%d%d%d", &a, &b, &c);
update(a, b, c, 1, n, 1);
}
printf("Case %d: The total value of the hook is %d.
", cas, sum[1]);
}
return 0;
}
POJ3468 A Simple Problem with Integers
- 题目链接:http://poj.org/problem?id=3468
- 线段树功能:
- update:成段更新;
- query:区间求和
#include <cstdio>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
const int maxn = 100010;
long long sum[maxn<<2], lazy[maxn<<2];
inline void push_up(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; }
inline void push_down(int rt, int len) {
if (lazy[rt]) {
int l_len = len-len/2, r_len = len/2;
lazy[rt<<1] += lazy[rt];
lazy[rt<<1|1] += lazy[rt];
sum[rt<<1] += l_len * lazy[rt];
sum[rt<<1|1] += r_len * lazy[rt];
lazy[rt] = 0;
}
}
void build(int l, int r, int rt) {
lazy[rt] = 0;
if (l == r) {
scanf("%lld", &sum[rt]);
return;
}
int m = (l + r) >> 1;
build(lson); build(rson); push_up(rt);
}
void update(int L, int R, long long val, int l, int r, int rt) {
if (L <= l && r <= R) {
lazy[rt] += val;
sum[rt] += (r - l + 1) * val;
return;
}
push_down(rt, r-l+1);
int m = (l + r) >> 1;
if (L <= m) update(L, R, val, lson);
if (R > m) update(L, R, val, rson);
push_up(rt);
}
long long query(int L, int R, int l, int r, int rt) {
if (L <= l && r <= R) return sum[rt];
push_down(rt, r-l+1);
int m = (l + r) >> 1;
long long res = 0;
if (L <= m) res += query(L, R, lson);
if (R > m) res += query(L, R, rson);
return res;
}
int n, q, a, b, c;
char op[2];
int main() {
scanf("%d%d", &n, &q);
build(1, n, 1);
while (q --) {
scanf("%s", op);
if (op[0] == 'C') {
scanf("%d%d%d", &a, &b, &c);
update(a, b, c, 1, n, 1);
} else {
scanf("%d%d", &a, &b);
printf("%lld
", query(a, b, 1, n, 1));
}
}
return 0;
}
POJ2528 Mayor‘s posters
题目链接:http://poj.org/problem?id=2528
题目大意:给你一个无限长的板子,然后依次往上面贴n张等高的海报,问你最后能看到多少张海报。
思路分析:线段树区间更新问题,但是要注意,给的长度的可能非常大,有1e9,不加处理直接维护一个线段树肯定会MLE,TLE,但是我们注意到一共最多只有2e4个点,因此我们可以用离散化的思想先对区间进行预处理。
但是注意简单的离散化可能会出现错误,给出下面两个简单的例子应该能体现普通离散化的缺陷:
例子一: 1-10 1-4 5-10
例子二: 1-10 1-4 6-10
普通离散化后都变成了 [1,4][1,2][3,4]
线段2覆盖了 [1,2]
,线段3覆盖了 [3,4]
,那么线段1是否被完全覆盖掉了呢?
例子一是完全被覆盖掉了,而例子二没有被覆盖
解决的办法则是对于距离大于1的两相邻点,中间再插入一个点,本题还用到了Lazy标记的思想
直接更新区间进行标记而先不对子节点进行处理,如果需要往下更新再将标记下传一层。
实现代码如下(我的代码里直接把数值存放在了延迟标记里):
#include <cstdio>
#include <algorithm>
using namespace std;
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
const int maxn = 100010;
long long lazy[maxn<<2];
inline void push_down(int rt, int len) {
if (lazy[rt]) {
int l_len = len-len/2, r_len = len/2;
lazy[rt<<1] = lazy[rt<<1|1] = lazy[rt];
lazy[rt] = 0;
}
}
void build(int l, int r, int rt) {
lazy[rt] = 0;
if (l == r) return;
int m = (l + r) >> 1;
build(lson); build(rson);
}
void update(int L, int R, int val, int l, int r, int rt) {
if (L <= l && r <= R) {
lazy[rt] = val;
return;
}
push_down(rt, r-l+1);
int m = (l + r) >> 1;
if (L <= m) update(L, R, val, lson);
if (R > m) update(L, R, val, rson);
}
int ans;
bool vis[maxn];
void query(int l, int r, int rt) {
if (l == r || lazy[rt]) {
if (lazy[rt] && !vis[ lazy[rt] ]) { vis[ lazy[rt] ] = true; ans ++; }
return;
}
int m = (l + r) >> 1;
query(lson); query(rson);
}
int T, n, l[maxn], r[maxn], num[maxn], mp[10000010], sz, cnt;
int main() {
scanf("%d", &T);
while (T --) {
scanf("%d", &n);
cnt = ans = 0;
for (int i = 1; i <= n; i ++) vis[i] = false;
for (int i = 1; i <= n; i ++) {
scanf("%d%d", &l[i], &r[i]);
num[cnt++] = l[i];
num[cnt++] = r[i];
}
sort(num, num+cnt);
cnt = unique(num, num+cnt) - num;
sz = 0;
for (int i = 0; i < cnt; i ++) {
if (i > 0 && num[i-1]+1 < num[i]) mp[ num[i]-1 ] = sz ++;
mp[ num[i] ] = sz ++;
}
build(0, sz-1, 1);
for (int i = 1; i <= n; i ++) {
int lnum = mp[ l[i] ];
int rnum = mp[ r[i] ];
update(lnum, rnum, i, 0, sz-1, 1);
}
query(0, sz-1, 1);
printf("%d
", ans);
}
return 0;
}
POJ3225 区间
题目链接:http://poj.org/problem?id=3225 本题有中文题面。
题目大意:区间操作,交、并、补等。
思路:
我们对操作进行分析得:
- U:把区间 ([l, r]) 覆盖成 1;
- I:把区间 ((- infty , l)) , ((r, + infty)) 覆盖成 0;
- D:把区间 ([l, r]) 覆盖成 0;
- C:把区间 ((- infty , l)) , ((r, + infty)) 覆盖成 0,且区间 ([l, r]) 0/1互换;
- S:把区间 ([l,r]) 0/1互换。
成段覆盖的操作很简单,比较特殊的就是区间 0/1 互换这个操作,我们可以称之为 异或操作。
对于懒惰标记:
- 覆盖标记:我们用0或者1来表示区间内是否都包含0或者1,用-1表示该区间内即包含1又包含0;
- 异或标记:是否进行了异或操作。
性质:当一个区间被覆盖后,不管之前有没有异或标记都没有意义了。
所以当一个节点得到覆盖标记时需要把异或标记清空;
而当一个节点得到异或标记的时候,先判覆盖标记,如果是0或者1,直接改变一下覆盖标记,不然的话改变异或标记。
开区间的话只要数字乘以2就可以处理了(偶数表示端点,奇数表示两端点之间的区间)
线段树功能:
- update:成段更新,区间异或;
- query:简单hash
代码:略。
练习
以上是关于线段树入门之成段更新的主要内容,如果未能解决你的问题,请参考以下文章
FZU 2105Digits Count(线段树 + 成段更新)
ZOJ 1610 Count the Colors (线段树成段更新)
POJ训练计划2777_Count Color(线段树/成段更新/区间染色)
POJ 2777 Count Color (线段树成段更新+二进制思维)