【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) ...
随机推荐
- 深入 理解char * ,char ** ,char a[ ] ,char *a[] 的区别(转)
C语言中由于指针的灵活性,导致指针能代替数组使用,或者混合使用,这些导致了许多指针和数组的迷惑,因此,刻意再次深入探究了指针和数组这玩意儿,其他类型的数组比较简单,容易混淆的是字符数组和字符指针这两个 ...
- FreeSql接入CAP的实践
CAP CAP 是一个基于 .NET Standard 的 C# 库,它是一种处理分布式事务的解决方案,同样具有 EventBus 的功能,它具有轻量级.易使用.高性能等特点. https://git ...
- 【译】Arc 在 Rust 中是如何工作的
原文标题:How Arc works in Rust 原文链接:https://medium.com/@DylanKerler1/how-arc-works-in-rust-b06192acd0a6 ...
- Spring扩展之二:ApplicationListener
1.介绍 用于监听应用程序事件的接口. 子接口:GenericApplicationListener,SmartApplicationListener. 通过ApplicationEvent类和App ...
- Dubbo 服务引入-Version2.7.5
1.服务引用原理 Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 Referen ...
- ctf-工具-binwalk
binwalk在玩杂项时是个不可缺的工具.1.最简单的,在玩隐写时,首先可以用它来找到其中的字符串例如:在铁人三项,东北赛区个人赛中,有一道题它直接给了一个文件,没有后缀,不知道是什么文件先binwa ...
- windbg 分析cpu异常
1. !threadpool 查看当前CPU状况 线程数等等 2. !runaway 查看那几个线程使用的高 建议多抓几个dump 然后确定到底是哪个线程 3. ~线程IDs 跳转到那个 ...
- 公司新来的小姐姐不懂java中的static关键字,这样给她描述不香吗?
前言 static关键字是摆在刚入行编程语言的小白们面前的一道难题,为什么要用static?使用它有什么好处?修饰方法和修饰变量有什么区别?本文将就java中static关键字的使用方法及注意事项进行 ...
- 什么是NTFS文件格式
说到磁盘格式,想必大家对于NTFS格式并不陌生.我们使用的u盘等硬盘设备很多都应用了此格式.NTFS文件格式究竟是什么?它都有哪些特点?今天,小编将利用这篇文章为大家进行介绍. 一.什么是NTFS文件 ...
- Java线程的死锁和活锁
目录 1.概览 2.死锁 2.1.什么是死锁 2.2 死锁举例 2.3 避免死锁 3.活锁 3.1 什么是活锁 3.2 活锁举例 3.3 避免活锁 1.概览 当多线程帮助我们提高应用性能的同时,它同时 ...