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+树剖+线段树】
		
圆方树不仅能解决仙人掌问题(虽然我仙人掌问题也没用过圆方树都是瞎搞过去的),还可以解决一般图的问题 一般图问题在于缩完环不是一棵树,所以就缩点双(包括双向边) 每个方点存他所在点双内除根以外的点的最小 ...
 
随机推荐
- 小米手机简单 ROOT教程(百分百成功)
			
大家都知道啊,由于小米自带的换机软件不支持一些应用数据的还原,所以需要使用钛备份来还原应用和数据.但是钛备份需要root才能用,因为有些机器刚出没多久,第三方的recovery也没有,所以需要找到一种 ...
 - go学习第一课--语法基础
			
一.hello world 新建文件helloworld.go package main import "fmt" func main() { fmt.Println( ...
 - ASP.NET Web 应用 Docker踩坑历程
			
听说Docker这玩意挺长时间了,新建Web应用的时候,也注意到有个启用Docker的选项. 前两天扫了一眼<[大话云原生]煮饺子与docker.kubernetes之间的关系>,觉得有点 ...
 - Infrastructure 知识: dnf对module的处理
			
引言 从RHEL8/CentOS8开始,dnf取代yum作为rpm 包管理工具.与之而来的还有模块(module)这个东西. 有了它们的加持,让在同一个OS上安装不同版本的软件或者开发语言的工作比之前 ...
 - Halo 开源项目学习(六):事件监听机制
			
基本介绍 Halo 项目中,当用户或博主执行某些操作时,服务器会发布相应的事件,例如博主登录管理员后台时发布 "日志记录" 事件,用户浏览文章时发布 "访问文章" ...
 - java.time包 时间处理类
			
已经习惯用 Date类这里就不再赘述,下面介绍新的时间处理类 1.LocalDate类 // 本地日期LocalDate localDate = LocalDate.of(2022, 2, 27);S ...
 - 服务器 CPU 100% 异常排查实践与总结
			
一个执着于技术的公众号 问题背景 昨天下午突然收到运维邮件报警,显示数据平台服务器cpu利用率达到了98.94%,而且最近一段时间一直持续在70%以上,看起来像是硬件资源到瓶颈需要扩容了,但仔细思考就 ...
 - 697. Degree of an Array - LeetCode
			
697. Degree of an Array - LeetCode Question 697. Degree of an Array - LeetCode Solution 理解两个概念: 数组的度 ...
 - Python模块Ⅰ
			
Python模块Ⅰ part1 模块的定义/取别名 自定义模块 什么是模块:模块的本质就是.py文件,封装语句的最小单位 模块中出现的变量,for循环,if结构,函数定义...称为模块成员 模块的运行 ...
 - 初识  Redis  以及其基本使用方法
			
1.什么是Redis redis 是一个高性能的key-value数据库,它支持的类型更多 包括 string(字符串).list(链表).set(集合).zset(sorted set --有序 ...