题目链接

思路分析

看到这道题首先想到的此题的树上版本。(不就是树链剖分的板子题么?)

但是此题是图上的两点间的走法,自然要想到是圆方树

我们先无脑构建出圆方树

我们先猜测:设后加入的节点权值为 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 题解的更多相关文章

  1. CF487E Tourists(圆方树+树链剖分+multiset/可删堆)

    CF487E Tourists(圆方树+树链剖分+multiset/可删堆) Luogu 给出一个带点权的无向图,两种操作: 1.修改某点点权. 2.询问x到y之间简单路径能走过的点的最小点权. 题解 ...

  2. [UOJ30]/[CF487E]Tourists

    [UOJ30]/[CF487E]Tourists 题目大意: 一个\(n(n\le10^5)\)个点\(m(m\le10^5)\)条边的无向图,每个点有点权.\(q(q\le10^5)\)次操作,操作 ...

  3. 【学习笔记】圆方树(CF487E Tourists)

    终于学了圆方树啦~\(≧▽≦)/~ 感谢y_immortal学长的博客和帮助 把他的博客挂在这里~ 点我传送到巨佬的博客QwQ! 首先我们来介绍一下圆方树能干什么呢qwq 1.将图上问题简化到树上问题 ...

  4. CF487E Tourists 【圆方树 + 树剖 + 堆】

    题目链接 CF487E 题解 圆方树 + 树剖 裸题 建好圆方树维护路径上最小值即可 方点的值为其儿子的最小值,这个用堆维护 为什么只维护儿子?因为这样修改点的时候就只需要修改其父亲的堆 这样充分利用 ...

  5. CF487E Tourists(圆方树+堆+链剖)

    本题解并不提供圆方树讲解. 所以不会圆方树的出门右转问yyb 没有修改的话圆方树+链剖. 方点的权值为点双连通分量里的最小值. 然后修改的话圆点照修,每一个方点维护一个小根堆. 考虑到可能被菊花卡死. ...

  6. CF487E Tourists[圆方树+树剖(线段树套set)]

    做这题的时候有点怂..基本已经想到正解了..结果感觉做法有点假,还是看了正解题解.. 首先提到简单路径上经过的点,就想到了一个关于点双的结论:两点间简单路径上所有可能经过的点的并等于路径上所有点所在点 ...

  7. CF487E Tourists 圆方树、树链剖分

    传送门 注意到我们需要求的是两点之间所有简单路径中最小值的最小值,那么对于一个点双联通分量来说,如果要经过它,则一定会经过这个点双联通分量里权值最小的点 注意:这里不能缩边双联通分量,样例\(2\)就 ...

  8. CF487E Tourists - Tarjan缩点 + 树剖 + multiset

    Solution 先Tarjan求出点双联通分量 并缩点. 用$multiset$维护 点双内的最小点权. 容易发现, 点双内的最小点权必须包括与它相连的割边的点权. 所以我们必须想办法来维护. 所以 ...

  9. CF487E Tourists【圆方树+tarjan+multiset+树剖+线段树】

    圆方树不仅能解决仙人掌问题(虽然我仙人掌问题也没用过圆方树都是瞎搞过去的),还可以解决一般图的问题 一般图问题在于缩完环不是一棵树,所以就缩点双(包括双向边) 每个方点存他所在点双内除根以外的点的最小 ...

随机推荐

  1. python基础练习题(题目 对10个数进行排序)

    day24 --------------------------------------------------------------- 实例037:排序 题目 对10个数进行排序. 分析:先输入1 ...

  2. WinUI使用LiteDB做个女演员图鉴

    为什么选择LiteDB 之前做uwp的时候有做过一个植物图鉴,当时图片使用的是在线图片,所以图片很多也并没有什么体验上的差别,但是直到有一天别人的网站挂掉了,图片访问不到了,当时想访问不到也没啥,反正 ...

  3. Ajax简单运用(JavaScript-----jQuery-------)

    首先引用 jQuery <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.4.1.min.js"> ...

  4. Django学习——Django settings 源码、模板语法之传值、模板语法之获取值、模板语法之过滤器、模板语法之标签、自定义过滤器、标签、inclusion_tag、模板的导入、模板的继承

    Django settings 源码 """ 1.django其实有两个配置文件 一个是暴露给用户可以自定义的配置文件 项目根目录下的settings.py 一个是项目默 ...

  5. kafka基础原理

    1.什么是kafka Kafka 是一个分布式的基于发布/订阅模式的消息队列 消息队列的两种模式: 点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除).特点,一个消息只能被一个消费者消费 发 ...

  6. vue - Vue脚手架/TodoList案例

    今天做了一个案例,可以好好做做能够将之前的内容结合起来,最主要的是能对组件化编码流程有一个大概的清晰认知,这一套做下来,明天自己再做一遍复习一下,其实组件化流程倒是基本上没什么问题了,主要是很多vue ...

  7. Linux磁盘和文件系统知识总结

    硬盘操作 为什么要给硬盘分区? 如果你需要在一块硬盘上用到多个文件系统,那么你就需要对硬盘进行分区,以便用不同的分区支持不同的文件系统.(但一个硬盘只能有一个分区表!)反过来说,如果你整块硬盘都用同样 ...

  8. react 疑问集锦

    在 setState 后未 re-render function component 初始化调用接口

  9. Java获取特定区间随机数及产生不重复随机数

    问题 有这样一种需求,在这样一个数组中String[] arr = new String[]{"电商", "互联网", "小程序", &qu ...

  10. Linux篇-The slave I/O thread stops because master and slave have equal...

    1)操作系统 cat /etc/issue CentOS release 6.6 (Final) Kernel \r on an \m cat /proc/version Linux version ...