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+树剖+线段树】
圆方树不仅能解决仙人掌问题(虽然我仙人掌问题也没用过圆方树都是瞎搞过去的),还可以解决一般图的问题 一般图问题在于缩完环不是一棵树,所以就缩点双(包括双向边) 每个方点存他所在点双内除根以外的点的最小 ...
随机推荐
- petite-vue源码剖析-逐行解读@vue-reactivity之effect
当我们通过effect将副函数向响应上下文注册后,副作用函数内访问响应式对象时即会自动收集依赖,并在相应的响应式属性发生变化后,自动触发副作用函数的执行. // ./effect.ts export ...
- 创建第一个c程序
创建,组织,生成 ,生成. 1.我们先创建一个win32项目. 文件->新建->项目->Visual C++ ->Win32 输入项目名称 选择项目保存位置 很重要的一 ...
- 基于SqlSugar的数据库访问处理的封装,支持.net FrameWork和.net core的项目调用
由于我们有时候需要在基于.net framework的项目上使用(如Winform端应用),有时候有需要在.net core的项目上使用(如.net core的WebAPI),那么我们把基于SQLSu ...
- python 本地配置文件库 Dynaconf 简介
[前言] 在项目中经常会遇到以下几种需要用到配置文件的场景: 相同的配置参数用在不同的代码中,如果需要调整,则需要手动将各个使用到的地方都相应调整. 密码等信息不想硬编码在项目文件中. 配置文件的格式 ...
- python写一个能变身电光耗子的贪吃蛇
python写一个不同的贪吃蛇 写这篇文章是因为最近课太多,没有精力去挖洞,记录一下学习中的收获,python那么好玩就写一个大一没有完成的贪吃蛇(主要还是跟课程有关o(╥﹏╥)o,课太多好烦) 第一 ...
- 面试官:ElasticSearch是什么,它有什么特性与使用场景?
哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 书接上回,我本以为我跟面试我的 ...
- 8.0 vue cli自定义页面
1.新建a.html(public文件夹下)文件,并设定div的id="a" <!DOCTYPE html> <html lang=""> ...
- 485. Max Consecutive Ones - LeetCode
Question 485. Max Consecutive Ones Solution 题目大意:给一个数组,取连续1的最大长度 思路:遍历数组,连续1就加1,取最大 Java实现: public i ...
- 144_Power Pivot贷款之等额本息与等额本金
博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 买房贷款的时候会遇到等额本息与等额本金的问题,今天做了一个两者对比,看看如何选择,来一张对比图. 等额本息的前期 ...
- JAVA数组案例!
数组的用例 一.案例需求: 有这样的一个数组,元素是{68,27,95,88,171,996,51,210}.求出该数组中满足要求的元素和, 要求是:求和的元素个位和十位都不能是7,并且只能是偶数如何 ...