线段树入门之成段更新

Posted codedecision

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树入门之成段更新相关的知识,希望对你有一定的参考价值。

  • 作者:zifeiy
  • 标签:线段树

HDU1698 Just a Hook

#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

#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 (线段树成段更新+二进制思维)

POJ3468_A Simple Problem with Integers(线段树/成段更新)

POJ 3468 A Simple Problem with Integers 线段树成段更新