- 作者: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.\n", 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\n", 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\n", 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
代码:略。