洛谷传送门:月下“毛景树”

由于没有合适的题目,就从这道题入手,解此题时用到的算法/数据结构包括:

  • 树链剖分
  • 线段树(区间覆盖、区间加、区间查询、单点修改)

这道题被我调试了四个小时才过,而且此题的测试点设置得极不友好,提交记录非0即100,每个点的数据范围都很大,几乎没有供调试的价值,所以推荐那些下载数据调试的朋友们,自己捏几组数据调试,没问题以后再和下载的数据对拍,可以说是能够对一个点就能全部AC了。

这篇博客不谈题解,只来说说线段树上的lazy标记如何下放


接下来进入正题

线段树的lazy标记

众所周知,线段树的单点修改、单点查询理论复杂度是O(logn)的,那么如果修改/查询m次,那么理论复杂度就达到O(mlogn),这样的复杂度是可以被接受的;但是如果一次修改/查询一个区间,并且连续进行m次这样的操作,那么最坏情况下的复杂度就上升到了O(mnlogn),这在1e5的数据范围下是很容易超时的,所以我们采取一种策略来降低复杂度——lazy标记

lazy就是将线段树上的操作推迟进行,具体推迟到什么时候呢?什么时候再次需要用到这一个区间了就什么时候再操作,也就是说如果这段区间不再被需要使用,那么这次操作是否执行就是对后续要进行的工作没有影响的,那么我们也就没有必要做这个操作,可以将它推迟到永远也不执行,以此实现对时间复杂度的优化。

举个例子:

现在我有一棵线段树,原序列编号从1开始,我希望进行的操作是将区间 [5,8] 的权值加一,并查询区间 [5,6] 的最值,按照单点修改的方法,就要在线段树上二分查找,直到找到了线段树的叶子节点,然后修改叶子节点的点权,再回溯修改上面的节点。

但我们会发现,这样一来,对 [7,8] 的修改操作是否进行是对答案没有影响的,所以我们就可以选择将对 [7,8] 的修改操作推迟,只进行 [5,6] 的修改操作,具体如何执行呢?

  1. 在维护线段树上每个节点信息的同时对每个节点维护一个名为“lazy”的变量
  2. lazy代表当前节点有(过)区间修改的操作
  3. 如果要对当前区间进行修改,那么修改信息的同时修改lazy值
  4. 维护好lazy信息后不再继续向下遍历,回溯即可
  5. 如果再次遍历到该节点,则下放lazy标记

如图所示:

进行区间修改时首先找到线段树上的区间 [l,r],满足 l >= 5 && r <= 8 ,然后对该区间打一个lazy标记并更新该区间信息,接着回溯,更新上面节点的信息。

这样第一步操作就结束了,接下来进行第二步操作,查询时依然用二分的方法找到一个区间 [l,r],满足 l >= 5 && r <= 6,并返回该区间的信息,最后合并所有返回的信息(这里我们只返回了一个区间,不需要合并)。

但出现了一个问题,要查询的区间因为lazy标记而导致操作推迟,返回的并不是正确的信息,怎么办呢?好说,在返回之前把被推迟的操作做了就行了。具体来说,就是在遍历过程中把被遍历到的节点的lazy标记下放

图中红色箭头表示遍历路径,凡是被遍历过并且lazy标记不为零的节点,都要对lazy标记进行下放,这样就能保证之前被推迟的操作在直接产生作用之前被执行。


区间覆盖与区间加的lazy下放

区间覆盖与区间加是两个完全不同的操作,自然就需要两个不同的lazy标记。这一部分我就拿例题中的线段树来举例好了。

struct node {int l, r, v;int lz = 0;int cvr = -1;
};
node tr[maxn << 2];

我们首先定义好线段树上的lazy标记和其他信息。其中 lz 表示区间加的 lazy,cvr 表示区间覆盖的 lazy。

int ask(int id, int tgtl, int tgtr);
int pls(int id, int tgtl, int tgtr, int k);
int cvr(int id, int tgtl, int tgtr, int k);

此题中涉及了区间查询、区间加、区间覆盖,所以我们定义以上三个函数(单点修改可以认为是在区间 [l,r] 上的区间修改,其中 l == r),参数 id 表示线段树上当前节点的编号,tgtl 和 tgtr表示目标区间的左端点和右端点,k 表示操作参数。

在这三个函数中,每到达一个节点,就应该有如下语句:

    int l = tr[id].l;int r = tr[id].r;    if (tr[id].cvr != -1) {if (l != r) cvrdown(id);    //如果不是叶子结点才需要下放tr[id].cvr = -1;tr[id].lz = 0;}if (tr[id].lz) {if (l != r) putdown(id);    //同上,判断叶子结点tr[id].lz = 0;}

其中 cvrdown(int id)putdown(int id) 是另外定义的两个函数,用来执行两个lazy标记的下放操作,函数的定义如下:

inline void cvrdown(int id) {tr[id << 1].v = tr[id].cvr;tr[id << 1 | 1].v = tr[id].cvr;tr[id << 1].cvr = tr[id].cvr;tr[id << 1 | 1].cvr = tr[id].cvr;return;
}inline void putdown(int id) {node ls = tr[id << 1];node rs = tr[id << 1 | 1];ls.v += tr[id].lz;rs.v += tr[id].lz;ls.lz += tr[id].lz;rs.lz += tr[id].lz;tr[id << 1] = ls;tr[id << 1 | 1] = rs;return;
}

值得注意的是,上面的代码是不正确的!如果覆盖操作在区间加操作之后,那么区间覆盖会掩盖之前的所有操作,所以cvr标记的下放可以将节点的cvr和lz都清空。但是!如果区间加操作在覆盖操作之后,那么区间加操作的结果是建立在区间覆盖的结果之上的,也就是在执行putdown函数时,要先检查儿子节点有没有 cvr 标记,如果有的话,先把 cvr 标记下放了,再下放 lz 标记,那么正确的代码如下

inline void cvrdown(int id) {tr[id << 1].v = tr[id].cvr;tr[id << 1 | 1].v = tr[id].cvr;tr[id << 1].cvr = tr[id].cvr;tr[id << 1 | 1].cvr = tr[id].cvr;return;
}inline void putdown(int id) {node ls = tr[id << 1];node rs = tr[id << 1 | 1];if (ls.cvr != -1) {if (ls.l != ls.r) cvrdown(id << 1);ls.cvr = -1;ls.lz = 0;}if (rs.cvr != -1) {if (rs.l != rs.r) cvrdown(id << 1 | 1);rs.cvr = -1;rs.lz = 0;}ls.v += tr[id].lz;rs.v += tr[id].lz;ls.lz += tr[id].lz;rs.lz += tr[id].lz;tr[id << 1] = ls;tr[id << 1 | 1] = rs;return;
}

这样一来lazy的下放就完成了!


        由于本篇博客主要是分享线段树lazy标记的下放,树链剖分的部分我就不多讲了,例题的AC代码如下:

#include<bits/stdc++.h>
using namespace std;
//月下“毛景树”
const int maxn = (int)1e5 + 10;
struct alled {int u, v, w;
};
struct edge {int to, vl;
};
alled e[maxn];
vector<edge> ed[maxn];
int n, cnt;
int dfn[maxn], dep[maxn], fa[maxn];
int top[maxn], sz[maxn], vl[maxn];
int fstson[maxn], opdfn[maxn];
struct node {int l, r, v;int lz = 0;int cvr = -1;
};
node tr[maxn << 2];void dfs1(int x) {sz[x] = 1;for (int i = 0; i < ed[x].size(); i++) {int y = ed[x][i].to;if (y == fa[x]) continue;fa[y] = x;dep[y] = dep[x] + 1;vl[y] = ed[x][i].vl;dfs1(y);sz[x] += sz[y];if (sz[y] > sz[fstson[x]]) fstson[x] = y;}return;
}void dfs2(int x, int t) {opdfn[++cnt] = x;dfn[x] = cnt;top[x] = t;if (fstson[x]) dfs2(fstson[x], t);for (int i = 0; i < ed[x].size(); i++) {int y = ed[x][i].to;if (y == fa[x] || y == fstson[x]) continue;dfs2(y, y);}return;
}void build(int id, int l, int r) {tr[id].l = l;tr[id].r = r;if (l == r) {tr[id].v = vl[opdfn[l]];return;}int mid = l + r >> 1;build(id << 1 | 1, mid + 1, r);build(id << 1, l, mid);tr[id].v = max(tr[id << 1].v, tr[id << 1 | 1].v);return;
}inline void cvrdown(int id) {tr[id << 1].v = tr[id].cvr;tr[id << 1 | 1].v = tr[id].cvr;tr[id << 1].cvr = tr[id].cvr;tr[id << 1 | 1].cvr = tr[id].cvr;return;
}inline void putdown(int id) {node ls = tr[id << 1];node rs = tr[id << 1 | 1];if (ls.cvr != -1) {if (ls.l != ls.r) cvrdown(id << 1);ls.cvr = -1;ls.lz = 0;}if (rs.cvr != -1) {if (rs.l != rs.r) cvrdown(id << 1 | 1);rs.cvr = -1;rs.lz = 0;}ls.v += tr[id].lz;rs.v += tr[id].lz;ls.lz += tr[id].lz;rs.lz += tr[id].lz;tr[id << 1] = ls;tr[id << 1 | 1] = rs;return;
}void pls(int id, int tgtl, int tgtr, int k) {int l = tr[id].l;int r = tr[id].r;if (tr[id].cvr != -1) {if (l != r) cvrdown(id);tr[id].cvr = -1;tr[id].lz = 0;}if (tr[id].lz) {if (l != r) putdown(id);tr[id].lz = 0;}if (tgtl <= l && r <= tgtr) {tr[id].v += k;tr[id].lz += k;return;}int mid = l + r >> 1;if (mid < tgtl) pls(id << 1 | 1, tgtl, tgtr, k);else if (mid >= tgtr) pls(id << 1, tgtl, tgtr, k);else {pls(id << 1 | 1, tgtl, tgtr, k);pls(id << 1, tgtl, tgtr, k);}tr[id].v = max(tr[id << 1].v, tr[id << 1 | 1].v);return;
}void cvr(int id, int tgtl, int tgtr, int k) {int l = tr[id].l;int r = tr[id].r;if (tr[id].cvr != -1) {if (l != r) cvrdown(id);tr[id].cvr = -1;tr[id].lz = 0;}if (tr[id].lz) {if (l != r) putdown(id);tr[id].lz = 0;}if (tgtl <= l && r <= tgtr) {tr[id].v = k;tr[id].lz = 0;tr[id].cvr = k;return;}int mid = l + r >> 1;if (mid < tgtl) cvr(id << 1 | 1, tgtl, tgtr, k);else if (mid >= tgtr) cvr(id << 1, tgtl, tgtr, k);else {cvr(id << 1 | 1, tgtl, tgtr, k);cvr(id << 1, tgtl, tgtr, k);}tr[id].v = max(tr[id << 1].v, tr[id << 1 | 1].v);return;
}int ask(int id, int tgtl, int tgtr) {int l = tr[id].l;int r = tr[id].r;if (tr[id].cvr != -1) {if (l != r) cvrdown(id);tr[id].cvr = -1;tr[id].lz = 0;}if (tr[id].lz) {if (l != r) putdown(id);tr[id].lz = 0;}if (tgtl <= l && r <= tgtr) return tr[id].v;int mid = l + r >> 1;if (mid < tgtl) return ask(id << 1 | 1, tgtl, tgtr);else if (mid >= tgtr) return ask(id << 1, tgtl, tgtr);else return max(ask(id << 1 | 1, tgtl, tgtr), ask(id << 1, tgtl, tgtr));
}inline void prepls(int x, int y, int k) {while (top[x] != top[y]) {if (dep[top[x]] > dep[top[y]]) {pls(1, dfn[top[x]], dfn[x], k);x = fa[top[x]];} else {pls(1, dfn[top[y]], dfn[y], k);y = fa[top[y]];}}if (x != y) dep[x] > dep[y] ? pls(1, dfn[y] + 1, dfn[x], k) : pls(1, dfn[x] + 1, dfn[y], k);return;
}inline void precvr(int x, int y, int k) {while (top[x] != top[y]) {if (dep[top[x]] > dep[top[y]]) {cvr(1, dfn[top[x]], dfn[x], k);x = fa[top[x]];} else {cvr(1, dfn[top[y]], dfn[y], k);y = fa[top[y]];}}if (x != y) dep[x] > dep[y] ? cvr(1, dfn[y] + 1, dfn[x], k) : cvr(1, dfn[x] + 1, dfn[y], k);return;
}inline int preask(int x, int y) {int ans = 0;while (top[x] != top[y]) {if (dep[top[x]] > dep[top[y]]) {ans = max(ans, ask(1, dfn[top[x]], dfn[x]));x = fa[top[x]];} else {ans = max(ans, ask(1, dfn[top[y]], dfn[y]));y = fa[top[y]];}}if (x != y) ans = max(ans, dep[x] > dep[y] ? ask(1, dfn[y] + 1, dfn[x]) : ask(1, dfn[x] + 1, dfn[y]));return ans;
}int main() {scanf("%d", &n);for (int i = 1; i < n; i++) {scanf("%d %d %d", &e[i].u, &e[i].v, &e[i].w);edge zhx;   zhx.vl = e[i].w;zhx.to = e[i].v;    ed[e[i].u].push_back(zhx);zhx.to = e[i].u;    ed[e[i].v].push_back(zhx);}dfs1(1);dfs2(1, 1);build(1, 1, n);register char s[10];register int u, v, w;while (true) {cin >> s;if (strlen(s) == 4) break;else if (strlen(s) == 5) {scanf("%d %d %d", &u, &v, &w);precvr(u, v, w);}else if (strlen(s) == 6) {scanf("%d %d", &u, &w);if (dep[e[u].u] < dep[e[u].v]) cvr(1, dfn[e[u].v], dfn[e[u].v], w);else cvr(1, dfn[e[u].u], dfn[e[u].u], w);} else if (s[0] == 'A') {scanf("%d %d %d", &u, &v, &w);prepls(u, v, w);} else {scanf("%d %d", &u, &v);printf("%d\n", preask(u, v));}}return 0;
}

【线段树】区间修改(区间覆盖、区间权值加)标记下放操作的逻辑顺序相关推荐

  1. 线段树模板题3:区间染色问题

    1.3线段树模板题3:区间染色问题 在DotA游戏中,帕吉的肉钩实际上是大多数英雄中最恐怖的东西.挂钩由长度相同的几个连续的金属棍组成. 现在,帕吉(Pudge)希望对挂接进行一些操作. 让我们将钩子 ...

  2. Zju2112 Dynamic Rankings(树状数组套可持久化权值线段树)

    Zju2112 Dynamic Rankings description solution code description 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须 ...

  3. Assign the task HDU - 3974(线段树+dfs建树+单点查询+区间修改)

    题意: 染色问题:给一个固定结构的树,现在有两个操作: (1) y 将结点x及其所有后代结点染成颜色y: (2)查询结点x当前的颜色. 其实就是区间染色问题,不过需要dfs预处理, 题目: There ...

  4. HDU 1166 敌兵布阵 【线段树-点修改--计算区间和】

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  5. codeforces round #576 div2 D Welfare State(线段树)[单点修改+区间修改]

    题意:有一些数字,以及一些操作.操作一是单点修改,输入1 b c,将位置b改成c,操作二是输入2 a,将不大于a的数全部改成a.求更改完毕后的数. tag的运用:tag是对被覆盖区间上加一个标记,那么 ...

  6. hdu1394线段树点修改,区间求和

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=1394 题意:给一个0-n-1的排列,这个排列中的逆序数为数对 (ai, aj) 满足 i < j a ...

  7. 树链剖分+线段树 单点修改 区间求和 模板

    马上要去西安打邀请赛了,存下板子 首先是vector存图的: #include<bits/stdc++.h> using namespace std; #define ll long lo ...

  8. NOI2016区间bzoj4653(线段树,尺取法,区间离散化)

    题目描述 在数轴上有 \(N\) 个闭区间 \([l_1,r_1],[l_2,r_2],...,[l_n,r_n]\) .现在要从中选出 \(M\) 个区间,使得这 \(M\) 个区间共同包含至少一个 ...

  9. NYOJ 1068 ST(线段树之 成段更新+区间求和)

    ST 时间限制:1000 ms  |  内存限制:65535 KB 难度:1 描述 "麻雀"lengdan用随机数生成了后台数据,但是笨笨的他被妹纸的问题给难住了... 已知len ...

最新文章

  1. LeetCode实战:最长回文子串
  2. EM 期望最大化算法
  3. windows下的正向shell
  4. 徐波 博士 计算机,徐波教授:医工联合促进智能肿瘤学发展——探秘肿瘤精准治疗中的AI技术...
  5. layui radio 根据获取的到值选中
  6. 程序员面试金典 - 面试题 16.14. 最佳直线(哈希map+set)
  7. 安卓手机浏览器排行_安卓手机性能排行榜:国产手机集体“出位”,华为却在角落哭泣?...
  8. clickhouse 数据存储
  9. Excel导入数据的实现
  10. QT界面添加背景图片
  11. Codeforces Round #829 (Div. 2) C1. Make Nonzero Sum (easy version) 解题报告
  12. Java、JSP小额支付管理平台
  13. Kattis - hoppers Hoppers(判奇环)
  14. linux内核调度 宿主机,Qemu虚拟机与宿主机之间文件传输
  15. tornado源码分析(四)之future、gen.coroutine
  16. 微服务下蓝绿部署、红黑部署、AB测试、灰度发布、金丝雀发布、滚动发布的概念与区别...
  17. day13_spring环境配置及bean使用
  18. ye lynn yama Loafer 已发送,请注意查收
  19. 梦幻西游 WSG 文件格式分析
  20. Docker容器的数据卷(volumes)

热门文章

  1. celery:File /home/hadoop/.virtualenvs/Django01/lib/python3.7/site-packages/fdfs_client/utils.py
  2. 地图服务 WMS WFS WCS TMS
  3. 欧拉回路(欧拉路径)
  4. 开发中国最好的视频推荐系统
  5. 携程在线网页制作(flex布局)(静态页面)
  6. 椭圆曲线密码算术(ECC)原理
  7. c语言程序设计教程+西安交通大学,大学C程序设计教程-西安交通大学.ppt
  8. 了matlab进行LR检验的代码,求助,matlab空间回归的LR检验显示错误该怎么办?
  9. 在ORACLE中用DBCA创建数据库
  10. 苹果基带坏了怎么办_iPhone12 上市,苹果这次有哪些改变