【SPOJ QTREE4】Query on a tree IV(树链剖分)
Description
给出一棵边带权(\(c\))的节点数量为 \(n\) 的树,初始树上所有节点都是白色。有两种操作:
C x
,改变节点 \(x\) 的颜色,即白变黑,黑变白。A
,询问树中最远的两个白色节点的距离,这两个白色节点可以重合(此时距离为 \(0\))。
\(q\) 次操作,输出所有查询的答案。
Hint
- \(1\le n, q\le 10^5\)
- \(0\le |c|\le 10^3\)
Solution
此题使用轻重链剖分真的麻烦
先树剖,然后根据每一个重链,建出一棵线段树(最后建出的是线段树森而非一棵大线段树,动态开点实现)。设 \(root(x)\) 为结点 \(x\) 对应线段树的根。非链顶结点的 \(root\) 无意义。
线段树上的每个结点维护 \(3\) 个字段:
- \(lx(x)\) 结点 \(x\) 代表的链上一段区间的 左端点(深度小的)可以到达 以链顶为根的子树 中最远的白点的距离。
- \(rx(x)\) 结点 \(x\) 代表的链上一段区间的 右端点(深度大的)可以到达 以链顶为根的子树 中最远的白点的距离。
- \(mx(x)\) 结点 \(x\) 代表满足 LCA 在当前结点区间中的所有白点对 中最大的距离。
那么我们可以这样设计我们的 pushup
函数,注意此处的 \(dep\) 带边权。
#define dis(x) dep[pos[x]] // dis(i) 表示区间中位置 i 所对应结点的深度
void pushup(int x, int l, int r) { // x 为线段树上当前结点,对应区间为 [l, r]
lx[x] = max(lx[lc[x]], lx[rc[x]] + dis(mid + 1) - dis(l));
// 可以从左儿子转移而来,也可以从右儿子跨越中间而来。
rx[x] = max(rx[rc[x]], rx[lc[x]] + dis(r) - dis(mid));
// 可以从右儿子转移而来,也可以从左儿子跨越中间而来。
mx[x] = max(max(mx[lc[x]], mx[rc[x]]), lx[rc[x]] + rx[lc[x]] + dis(mid + 1) - dis(mid));
// 可以从儿子结点转移而来,或者计算出跨越中心情况的答案。
// dis 的差值实质上是边权
}
#undef dis
答案即为 \(\max\{mx\}\)。
如何处理叶结点的值?显然不能爆算子树中所有结点的距离,因为深度总和会达到 \(O(n^2)\) 级别。
对于一个 线段树上 的叶子结点 \(x\),其对应的 原树 结点为 \(u\)。对于其 父结点或重儿子,由于在同一条链上 ,无需过多考虑。\(u\) 的所有轻儿子,显然它们一定是其所在链的链顶。
对于其中一个轻儿子 \(v\),易知 \(lx(root(v))\) 子树 \(v\) 中向下延伸的最长合法路径。那么加上当前路径就是 一条“LCA 位于区间 \([dfn(u), dfn(u)]\)”的合法路径。(\(dfn(x)\) 表示原树上结点 \(x\) 的 dfs 序)。
设 \(d_1\) 为一端为 \(u\) 的 最长向下 路径长,\(d_2\) 为 次长向下 路径长。不存在设为 \(-\infty\)。
不难得出,\(lx(x), rx(x)\) 的值就是 \(\max(d_1, 0)\)。\(mx(x)\) 的值可以由最长、次长两条路径拼成。无需考虑路径会不会重合,因为来自不同的子树。由于白点自身可以作为路径的端点,\(mx(x)\) 的值需要分类讨论。
- 白点:\(mx(x) = \max(d_1, d_1 + d_2, 0)\)。
- 黑点:\(mx(x) = \max(d_1 + d_2, 0)\)。
对于最大值、次大值的维护,可以使用堆。在叶结点遍历轻儿子时顺便将堆更新。求次大值时只需将堆顶弹出,取值后重新塞回即可。
答案即为 \(\max\limits_{x\in \text{tops}} \{ mx(root(x))\}\),同样可以用一个全局堆维护。
建树操作参考代码:
void build(int& x, int l, int r) {
if (!x) x = ++total; // 动态开点
if (l == r) {
int u = pos[l];
getEdge(u, v) if (v->to != fa[u] && v->to != wson[u])
pt[u].insert(lx[root[v->to]] + dep[v->to] - dep[u]);
// pt 为堆
int d1 = pt[u].top(); // 最大
pt[u].erase(d1);
int d2 = pt[u].top(); // 次大
pt[u].insert(d1);
lx[x] = rx[x] = max(d1, 0);
mx[x] = max(d1, max(d1 + d2, 0));
return;
}
build(lc[x], l, mid);
build(rc[x], mid + 1, r);
pushup(x, l, r);
}
考虑修改操作。一个修改可能 会影响到其祖先的答案,于是我们需要一直向上跳。
设当前跳到的位置为 \(x\),上次位置的链顶为 \(y\)。
首先在 链顶父亲 结点的堆中删去 当前链顶的贡献,下一次跳在重新将 更新过的值插入。
那么在线段树上修改时,将堆中 \(y\) 方向轻儿子的贡献 重新插入 \(x\) 的堆中,像 build
一样维护即可。
同时别忘了更新全局堆。
下面给出修改的代码:
void update(int x, int l, int r, int u, int v) {
if (l == r) {
if (u != v)
pt[u].insert(lx[root[v]] + dep[v] - dep[u]);
int d1 = pt[u].top();
pt[u].erase(d1);
int d2 = pt[u].top();
pt[u].insert(d1);
if (color[u]) {
lx[x] = rx[x] = d1;
mx[x] = d1 + d2;
} else {
lx[x] = rx[x] = max(d1, 0);
mx[x] = max(d1, max(d1 + d2, 0));
}
return;
}
if (dfn[u] <= mid) update(lc[x], l, mid, u, v);
else update(rc[x], mid + 1, r, u, v);
pushup(x, l, r);
}
void change(int x) {
color[x] ^= 1;
if (color[x] == 0) ++white;
else --white;
for (int y = x; x; x = fa[x]) {
int top = wtop[x];
all.erase(mx[root[top]]);
if (fa[top]) pt[fa[top]].erase(lx[root[top]] + dep[top] - dep[fa[top]]);
update(root[top], dfn[top], dfn[top] + len[top] - 1, x, y);
all.insert(mx[root[top]]);
y = x = top;
}
}
那么算法基本算是完成了。
但实现非常复杂,细节多(上面代码)。
不过实测表现不差,原因是树剖、线段树的 \(\log\) 都跑不满。
时间复杂度 \(O(n\log^2 n)\)。
参考代码:https://vjudge.net/solution/26745510/8T7tgRJPSKwBPUqVL9r2
【SPOJ QTREE4】Query on a tree IV(树链剖分)的更多相关文章
- SPOJ QTREE4 - Query on a tree IV 树分治
题意: 给出一棵边带权的树,初始树上所有节点都是白色. 有两种操作: C x,改变节点x的颜色,即白变黑,黑变白 A,询问树中最远的两个白色节点的距离,这两个白色节点可以重合(此时距离为0). 分析: ...
- QTREE3 spoj 2798. Query on a tree again! 树链剖分+线段树
Query on a tree again! 给出一棵树,树节点的颜色初始时为白色,有两种操作: 0.把节点x的颜色置反(黑变白,白变黑). 1.询问节点1到节点x的路径上第一个黑色节点的编号. 分析 ...
- spoj 375 Query on a tree(树链剖分,线段树)
Query on a tree Time Limit: 851MS Memory Limit: 1572864KB 64bit IO Format: %lld & %llu Sub ...
- SPOJ 375 Query on a tree(树链剖分)(QTREE)
You are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, ...
- SPOJ QTREE - Query on a tree 【树链剖分模板】
题目链接 引用到的大佬博客 代码来自:http://blog.csdn.net/jinglinxiao/article/details/72940746 具体算法讲解来自:http://blog.si ...
- SPOJ 375 Query on a tree(树链剖分)
https://vjudge.net/problem/SPOJ-QTREE 题意: 给出一棵树,树上的每一条边都有权值,现在有查询和更改操作,如果是查询,则要输出u和v之间的最大权值. 思路: 树链剖 ...
- SPOJ QTREE Query on a Tree【树链剖分模板题】
树链剖分,线段树维护~ #include <cstdio> #include <cstring> #include <iostream> #include < ...
- SPOJ 375. Query on a tree (树链剖分)
Query on a tree Time Limit: 5000ms Memory Limit: 262144KB This problem will be judged on SPOJ. Ori ...
- SPOJ 375 Query on a tree【树链剖分】
题目大意:给你一棵树,有两个操作1.修改一条边的值,2.询问从x到y路径上边的最大值 思路:如果树退化成一条链的话线段树就很明显了,然后这题就是套了个树连剖分,调了很久终于调出来第一个模板了 #inc ...
- SPOJ QTREE6 Query on a tree VI 树链剖分
题意: 给出一棵含有\(n(1 \leq n \leq 10^5)\)个节点的树,每个顶点只有两种颜色:黑色和白色. 一开始所有的点都是黑色,下面有两种共\(m(1 \leq n \leq 10^5) ...
随机推荐
- binary hacks读数笔记(装载)
1.地址空间 在linux系统中,每个进程拥有自己独立的虚拟地址空间,这个虚拟地址空间的大小是由计算机硬件决定的,具体地说,是由CPU的位数决定的.比如,32位硬件平台决定的虚拟地址空间大小:0--2 ...
- jm8.6编解码器概述
自己在学习h264的路上,欢迎讨论交流. 前段时间研究JM出品的h264编码器,代码实在看不下去,因此换了个角度来研究诸多算法--逆向方式(解码),本系列文章记录一些遇到的东西和思考. 1. JM介绍 ...
- explain命令---查看mysql执行计划
引言: 实际项目开发中,由于我们不知道实际查询的时候数据库里发生了什么事情,数据库软件是怎样扫描表.怎样使用索引的,因此,我们能感知到的就只有 sql语句运行的时间,在数据规模不大时,查询是瞬间的,因 ...
- 解决calamari无法获取节点信息的bug
前言 一直在做calamari的相关的一些打包和安装的工作,都是业余弄的东西,所以并没有仔细的进行功能点的验证测试,正好ceph社区群里面有人问了个问题 calamari上是不是能看到ceph的ver ...
- Java编译程序和运行过程详解
java整个编译以及运行的过程相当繁琐,我就举一个简单的例子说明: 编译原理简单过程:词法分析 --> 语法分析 --> 语义分析和中间代码生成 --> 优化 --> 目标代码 ...
- 移动端调试Web页面
移动端调试Web页面 虽然可以在PC下,通过开发者工具,模拟移动端,但是这样只能模拟页面样式,对于代码的执行情况是无法模拟的,所以在此结合实际调试经验,针对安卓与IOS设备,进行总结. IOS 安卓 ...
- 怎么借助CrossOver安装想要的Windows程序
面对安装双系统时的繁琐步骤,以及虚拟机软件那庞大的体积,CrossOver的出现,让一切都变得简单起来. CrossOver自带的一系列的Windows应用,涵盖游戏软件.办公软件.设计软件等多个种类 ...
- appium 启动参数配置
启动配置参数,可以参照官网: http://appium.io/docs/en/writing-running-appium/caps/#general-capabilities from appiu ...
- Requests 库的使用
Python 的标准库 urllib 提供了大部分 HTTP 功能,但使用起来较繁琐.通常,我们会使用另外一个优秀的第三方库:Requests,它的标语是:Requests: HTTP for Hum ...
- yii的pathinfo方式实现
yii2.0在浏览器中默认查看控制器下的方法是 http://ltbk.cn/index.php?r=login/login 要是在浏览器上输出 http://ltbk.cn/index.php/l ...