【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) ...
随机推荐
- 预估ceph的迁移数据量
引言 我们在进行 ceph 的 osd 的增加和减少的维护的时候,会碰到迁移数据,但是我们平时会怎么去回答关于迁移数据量的问题,一般来说,都是说很多,或者说根据环境来看,有没有精确的一个说法,到底要迁 ...
- Python _PyQt5 【总】
http://www.cnblogs.com/archisama/p/5442071.html QtCore QtGui QtWidgets QtMultimedia QtBluetooth QtNe ...
- ubuntu16.04搭建vulhub环境
简介 Vulhub官方中文教程https://github.com/vulhub/vulhub/blob/master/README.zh-cn.md 环境:ubuntu16.04.5 python3 ...
- [web安全原理]PHP命令执行漏洞基础
前言 PHP命令执行漏洞 应用程序的某些功能功能需要调用可以执行系统命令的函数,如果这些函数或者函数的参数被用户控制,就有可能通过命令连接符将恶意命令拼接到正常的函数中,从而随意执行系统命令,这就是命 ...
- python-网络安全编程第三天(正则表达式)
python 正则表达式 正则表达式本身是一种小型的.高度专业化的编程语言,而在python中,通过内嵌集成re模块,程序媛们可以直接调用来实现正则匹配.正则表达式模式被编译成一系列的字节码,然后由用 ...
- python实现一个无序单链表
class Node: """先定一个node的类""" def __init__(self, value=None, next=None) ...
- Vegas实战——如何导入导出视频
Vegas作为一款专业的视频非编软件,在国内受到了很多用户的喜爱.小编认为,对于很多用户来说,他们选择sony vegas的一个原因是vegas在不论是从产品性能,还是使用效果上,都很容易被用户接受. ...
- Beyond Compare-这款检查图片工具真的绝了!
无论是出去旅游,还是参加聚会,在朋友圈分享美美的图片,已经成为了很多都市人的日常.在分享图片前,大多数人都会选择使用滤镜.风格等功能对照片进行一定的美化.但有时候美化的程度比较轻微,连修图的人都无法判 ...
- FL Studio通道常规设置
每个通道设置窗口都包含声相.音量.音高和混音音轨等.刚学习FL Studio的同学可能对这些旋钮的功能还不是很了解,所以也就直接导致了不能很好的运用.为了帮助同学进一步熟悉这款软件,小编今天将为大家详 ...
- windows安装redis扩展
Thread Safety enabled 打开phpinfo() 看php版本是ts还是nts, 如上是ts版本的,所以需要安装redis的ts版本, redis的扩展下载地址 https://p ...