CF487E Tourists 题解
思路分析
看到这道题首先想到的此题的树上版本。(不就是树链剖分的板子题么?)
但是此题是图上的两点间的走法,自然要想到是圆方树。
我们先无脑构建出圆方树。
我们先猜测:设后加入的节点权值为 inf,直接再圆方树上做述链剖分?
看上去很简单,但是完全不对,考虑同一个点双的情况。
他们在圆方树中是兄弟的关系,根本剖不到上面去,但是同样对答案有贡献。
所以不能树链剖分去做? NO!
我们来模拟一下这组数据:
n = 10 m = 11
1 2
2 3
1 4
3 4
4 5
5 6
4 7
6 7
6 9
6 10
9 10
接下来给出原图和原图相对应的圆方图:


有一个大胆的设想:因为每一个新建的方形节点都对应一个点双,是不是可以把新建的节点权值更新成和它相连的所有节点权值的最小值呢?
好像不对呀,要是像途中 \(4\) 节点一样怎么办啊,一旦向左走就出不去了。
其实根本不要这么担心,仔细模拟一下发现:会出现上面这种情况的点对路径不会经过 \(13\) ! (看来是想多了)
于是我们得到以下思路:
- 先构建出圆方树。
- 每个新节点为与它相连的节点的权值最小值。
- 树链剖分,每次修改时更新对应的新节点。
看上去非常完美,结果 TLE 在了第 \(18\) 个点。
来看下面的一个图:(你就会怀疑人生)

此时我们的 \(n=5\),这个图还有个名字——菊花图,成功把我们卡到了 \(O(n^2)\)。
那么我们之前的要推翻重来吗?不是我们来考虑圆方树的性质。
因为圆方树就是树,我们直接从树的性质入手。
考虑到每个树上节点只会有一个父亲,那么我们可以把一个方节点记录其子节点的最小值。
所以有什么区别么?有!这样的话我们每个更新就从他的所连接点变成了父亲节点!
我们对每一个方形节点建一棵平衡树,维护一下最小值。(我用的是 \(multiset\))
注意:当两个节点的 \(lca\) 是方形节点时,要考虑方形节点的的父亲节点。
Code
代码还是很清新的 \tuu
#include <bits/stdc++.h>
#define file(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout)
#define Enter putchar('\n')
#define quad putchar(' ')
namespace IO {
template <class T> inline void read(T &a);
template <class T, class ...rest> inline void read(T &a, rest &...x);
template <class T> inline void write(T x);
template <class T, class ...rest> inline void write(T x, rest ...a);
}
#define N 200005
int n, m, Q, w[N], ww[N];
int tot2, head2[N], to2[N], then2[N], val2[N];
int tot, head[2 * N], to[2 * N], then[2 * N], val[2 * N];
int dfn[N], low[N], tp, sta[N], dfnnum, cnt;
int ff[N][30];
int seg[N], rev[4 * N], siz[N], son[N], top[N], dep[N], fa[N], mn[4 * N];
char c[3];
std::multiset <int> st[N];
inline void addline(int x, int y) {
tot ++;
to[tot] = y;
then[tot] = head[x];
head[x] = tot;
}
inline void addline2(int x, int y) {
tot2 ++;
to2[tot2] = y;
then2[tot2] = head2[x];
head2[x] = tot2;
}
inline void tarjan(int now) {
dfn[now] = low[now] = ++dfnnum;
sta[++tp] = now;
for (int i = head2[now]; i; i = then2[i]) {
int t = to2[i];
if (!dfn[t]) {
tarjan(t);
low[now] = std::min(low[now], low[t]);
if (low[t] == dfn[now]) {
++cnt;
while (tp && sta[tp] != t) {
addline(cnt, sta[tp]);
addline(sta[tp], cnt);
tp--;
}
addline(cnt, sta[tp]);
addline(sta[tp], cnt);
tp--;
addline(now, cnt);
addline(cnt, now);
}
} else
low[now] = std::min(low[now], dfn[t]);
}
}
inline int LCA(int x, int y) {
if (dep[x] < dep[y]) std::swap(x, y);
for (int i = 20;i >= 0; i--)
if (dep[ff[x][i]] >= dep[y])
x = ff[x][i];
if (x == y) return x;
for (int i = 20; i >= 0; i--)
if (ff[x][i] != ff[y][i])
x = ff[x][i], y = ff[y][i];
return ff[x][0];
}
namespace Segt {
inline void dfs1(int now, int father);
inline void dfs2(int now, int father);
inline void build(int k, int l, int r);
inline void modify(int k, int l, int r, int pos, int val);
inline int query(int k, int l, int r, int x, int y);
inline int solve(int x, int y);
}
signed main(void) {
// file("CF487E");
IO::read(n, m, Q);
for (int i = 1; i <= n; i++) IO::read(w[i]);
for (int i = 1, x, y; i <= m; i++) {
IO::read(x, y);
addline2(x, y); addline2(y, x);
}
cnt = n;
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i), tp--;
// for (int i = 1; i <= cnt; i++) {
// for (int j = head[i]; j; j = then[j])
// printf ("%d %d\n", i, to[j]);
// }
Segt::dfs1(1, 0);
for (int i = n + 1; i <= cnt; i++) {
w[i] = 0x3f3f3f3f;
for (int j = head[i]; j; j = then[j]){
if (dep[to[j]] > dep[i]) {
st[i].insert(w[to[j]]);
w[i] = std::min(w[i], w[to[j]]);
}
}
}
for (int i = 1; i <= cnt; i++)
ww[i] = w[i];
seg[0] = seg[1] = top[1] = rev[1] = 1;
Segt::dfs2(1, 0);
Segt::build(1, 1, seg[0]);
for (int test = 1, a, b; test <= Q; test++) {
scanf("%s", c + 1);
IO::read(a, b);
if (c[1] == 'C') {
int last = ww[a];
ww[a] = b;
Segt::modify(1, 1, seg[0], seg[a], b);
if (fa[a] == 0) continue;
// printf ("!");
int father = fa[a];
st[father].erase(last);
st[father].insert(b);
ww[father] = *st[father].begin();
Segt::modify(1, 1, seg[0], seg[father], ww[father]);
} else {
int lca = LCA(a, b);
// printf("%d %d %d\n", lca, fa[lca], ww[lca]);
int ans = Segt::solve(a, b);
if (lca > n)
ans = std::min(ans, ww[fa[lca]]);
IO::write(ans);
Enter;
}
}
}
namespace Segt {
inline void dfs1(int now, int father) {
siz[now] = 1;
fa[now] = father;
ff[now][0] = father;
dep[now] = dep[father] + 1;
for (int i = 0; i < 25; i++)
ff[now][i + 1] = ff[ff[now][i]][i];
for (int i = head[now]; i; i = then[i]) {
int t = to[i];
if (t == father) continue;
dfs1(t, now);
siz[now] += siz[t];
if (siz[t] > siz[son[now]]) son[now] = t;
}
}
inline void dfs2(int now, int father) {
if (son[now]) {
seg[son[now]] = ++seg[0];
top[son[now]] = top[now];
rev[seg[0]] = son[now];
dfs2(son[now], now);
}
for (int i = head[now]; i; i = then[i]) {
int t = to[i];
if (top[t]) continue;
seg[t] = ++seg[0];
rev[seg[0]] = t;
top[t] = t;
dfs2(t, now);
}
}
inline void build(int k, int l, int r) {
if (l == r) {
mn[k] = w[rev[l]];
return ;
}
int mid = (l + r) / 2;
build(k * 2, l, mid);
build(k * 2 + 1, mid + 1, r);
mn[k] = std::min(mn[k * 2], mn[k * 2 + 1]);
}
inline void modify(int k, int l, int r, int pos, int val) {
if (pos > r || pos < l) return ;
if (l == r && l == pos) {
mn[k] = val;
return ;
}
int mid = (l + r) / 2;
if (mid >= pos) modify(k * 2, l, mid, pos, val);
if (mid < pos) modify(k * 2 + 1, mid + 1, r, pos, val);
mn[k] = std::min(mn[k * 2], mn[k * 2 + 1]);
}
inline int query(int k, int l, int r, int x, int y) {
if (x > r || y < l) return 0x3f3f3f3f;
if (l >= x && r <= y) return mn[k];
int mid = (l + r) / 2;
if (mid >= x) {
if (mid < y) {
int min1, min2;
min1 = query(k * 2, l, mid, x, y);
min2 = query(k * 2 + 1, mid + 1, r, x, y);
return std::min(min1, min2);
} else
return query(k * 2, l, mid, x, y);
} else
return query(k * 2 + 1, mid + 1, r, x, y);
}
inline int solve(int x, int y) {
int fx = top[x], fy = top[y];
int ret = 0x3f3f3f3f;
while (fx != fy) {
if (dep[fx] < dep[fy])
std::swap(x, y), std::swap(fx, fy);
ret = std::min(ret, query(1, 1, seg[0], seg[fx], seg[x]));
x = fa[fx]; fx = top[x];
}
if (dep[x] > dep[y]) std::swap(x, y);
ret = std::min(ret, query(1, 1, seg[0], seg[x], seg[y]));
return ret;
}
}
namespace IO {
template <class T> inline void read(T &a) {
T s = 0, t = 1;
char c = getchar();
while ((c < '0' || c > '9') && c != '-')
c = getchar();
if (c == '-')
c = getchar(), t = -1;
while (c >= '0' && c <= '9')
s = (s << 1) + (s << 3) + (c ^ 48), c = getchar();
a = s * t;
}
template <class T, class ...rest> inline void read(T &a, rest &...x) {
read(a); read(x...);
}
template <class T> inline void write(T x) {
if (x == 0) putchar('0');
if (x < 0) putchar('-'), x = -x;
int top = 0, sta[50] = {0};
while (x)
sta[++top] = x % 10, x /= 10;
while (top)
putchar(sta[top] + '0'), top --;
return ;
}
template <class T, class ...rest> inline void write(T x, rest ...a) {
write(x); quad; write(a...);
}
}
CF487E Tourists 题解的更多相关文章
- CF487E Tourists(圆方树+树链剖分+multiset/可删堆)
CF487E Tourists(圆方树+树链剖分+multiset/可删堆) Luogu 给出一个带点权的无向图,两种操作: 1.修改某点点权. 2.询问x到y之间简单路径能走过的点的最小点权. 题解 ...
- [UOJ30]/[CF487E]Tourists
[UOJ30]/[CF487E]Tourists 题目大意: 一个\(n(n\le10^5)\)个点\(m(m\le10^5)\)条边的无向图,每个点有点权.\(q(q\le10^5)\)次操作,操作 ...
- 【学习笔记】圆方树(CF487E Tourists)
终于学了圆方树啦~\(≧▽≦)/~ 感谢y_immortal学长的博客和帮助 把他的博客挂在这里~ 点我传送到巨佬的博客QwQ! 首先我们来介绍一下圆方树能干什么呢qwq 1.将图上问题简化到树上问题 ...
- CF487E Tourists 【圆方树 + 树剖 + 堆】
题目链接 CF487E 题解 圆方树 + 树剖 裸题 建好圆方树维护路径上最小值即可 方点的值为其儿子的最小值,这个用堆维护 为什么只维护儿子?因为这样修改点的时候就只需要修改其父亲的堆 这样充分利用 ...
- CF487E Tourists(圆方树+堆+链剖)
本题解并不提供圆方树讲解. 所以不会圆方树的出门右转问yyb 没有修改的话圆方树+链剖. 方点的权值为点双连通分量里的最小值. 然后修改的话圆点照修,每一个方点维护一个小根堆. 考虑到可能被菊花卡死. ...
- CF487E Tourists[圆方树+树剖(线段树套set)]
做这题的时候有点怂..基本已经想到正解了..结果感觉做法有点假,还是看了正解题解.. 首先提到简单路径上经过的点,就想到了一个关于点双的结论:两点间简单路径上所有可能经过的点的并等于路径上所有点所在点 ...
- CF487E Tourists 圆方树、树链剖分
传送门 注意到我们需要求的是两点之间所有简单路径中最小值的最小值,那么对于一个点双联通分量来说,如果要经过它,则一定会经过这个点双联通分量里权值最小的点 注意:这里不能缩边双联通分量,样例\(2\)就 ...
- CF487E Tourists - Tarjan缩点 + 树剖 + multiset
Solution 先Tarjan求出点双联通分量 并缩点. 用$multiset$维护 点双内的最小点权. 容易发现, 点双内的最小点权必须包括与它相连的割边的点权. 所以我们必须想办法来维护. 所以 ...
- CF487E Tourists【圆方树+tarjan+multiset+树剖+线段树】
圆方树不仅能解决仙人掌问题(虽然我仙人掌问题也没用过圆方树都是瞎搞过去的),还可以解决一般图的问题 一般图问题在于缩完环不是一棵树,所以就缩点双(包括双向边) 每个方点存他所在点双内除根以外的点的最小 ...
随机推荐
- 终极套娃 2.0|云原生 PaaS 平台的可观测性实践分享
某个周一上午,小涛像往常一样泡上一杯热咖啡 ️,准备打开项目协同开始新一天的工作,突然隔壁的小文喊道:"快看,用户支持群里炸锅了 -" 用户 A:"Git 服务有点问题, ...
- XCTF练习题---MISC---神奇的Modbus
XCTF练习题---MISC---神奇的Modbus flag:sctf{Easy_Modbus} 解题步骤: 1.观察题目,下载附件 2.打开下载文件,发现可以用WireShark打开,打开看看是啥 ...
- 干货|带你体验一次原生OpenStack云平台发放云主机的过程
一个执着于技术的公众号 1 前言 上一章节我们完成了OpenStack云平台的搭建工作,今天就带大家一起学习下如何发放一台云主机 点击查看:如何搭建一套OpenStack云平台 2 发放OpenSta ...
- 年年出妖事,一例由JSON解析导致的"薛定谔BUG"排查过程记录
前言 做开发这么多年,也碰到无数的bug了.不过再复杂的bug,只要仔细去研读代码,加上debug,总能找到原因. 但是最近公司内碰到的这一个bug,这个bug初看很简单,但是非常妖孽,在一段时间内我 ...
- p2p-tunnel 打洞内网穿透系列(二)TCP转发访问内网共享文件夹
系列文章 p2p-tunnel 打洞内网穿透系列(一)客户端配置及打洞 p2p-tunnel 打洞内网穿透系列(二)TCP转发访问远程共享文件夹 p2p-tunnel 打洞内网穿透系列(三)TCP转发 ...
- ajax 请求登录超时跳转登录页解决方法
在Filter里判断是否登录,如果未登录返回401状态 public class SelfOnlyAttribute : ActionFilterAttribute { public override ...
- Spring事务源码解读
一.Spring事务使用 1.通过maven方式引入jar包 <dependency> <groupId>com.alibaba</groupId> <art ...
- kernel 劫持seq_operations && 利用pt_regs
kernel 劫持seq_operations && 利用pt_regs 劫持seq_operations进行栈迁移 seq_operations是一个大小为0x20的结构体,在打开/ ...
- SSE图像算法优化系列三十二:Zhang\Guo图像细化算法的C语言以及SIMD指令优化
二值图像的细化算法也有很多种,比较有名的比如Hilditch细化.Rosenfeld细化.基于索引表的细化.还有Opencv自带的THINNING_ZHANGSUEN.THINNING_GUOHALL ...
- 内网穿透frp教程 windows远程桌面连接
鉴于ngrok不是特别好用 昨天又发现frp这个神器 在管理端还有图形界面十分友好 话不多说开始 准备工作 1.一个域名 2.一台服务器 一.域名与服务器 域名和服务器直接买就好咯 价格不高 一定要在 ...