前言

圆方树学习笔记,从一道例题讲起。

题目链接:Hydro & bzoj

题意简述

仙人掌上求两点距离。

题目分析

为了把仙人掌的性质发挥出来,考虑将其变成一棵树。圆方树就是这样转换的工具。

先讲讲圆方树的概念:原图上的点为圆点,每个点双对应一个方点,树边都是方点连向点双内的圆点。

具体代码实现也十分简单,就是在 tarjan 求点双的时候,弹栈的时候,新建结点,并和点双内的点连边即可。或者在处理最后连边即可。

注意,对于孤立点的处理,是保持原样不动,还是变为一对方点和圆点,视情况讨论。以下代码按照上述定义,即对于孤立点,在圆方树上体现为一对方点和白点。

void tarjan(int now, int fa){
dfn[now] = low[now] = ++timer, stack[++top] = now;
int son = 0;
for (int i = head[now]; i; i = edge[i].nxt){
int to = edge[i].to;
if (!dfn[to]){
tarjan(to, now), low[now] = min(low[now], low[to]), ++son;
if (low[to] >= dfn[now]){
scc[++scc_cnt].push_back(now);
do scc[scc_cnt].push_back(stack[top--]) while (stack[top + 1] != to);
}
} else if (to != fa) low[now] = min(low[now], dfn[to]);
}
if (!son && !fa) scc[++scc_cnt].push_back(now);
}
for (int i = 1; i <= scc_cnt; ++i)
for (const auto& u: scc[i]) {
yzh.add(i + n, u);
yzh.add(u, i + n);
}
void tarjan(int now, int fa){
dfn[now] = low[now] = ++timer, stack[++top] = now;
int son = 0;
for (int i = head[now]; i; i = edge[i].nxt){
int to = edge[i].to;
if (!dfn[to]){
tarjan(to, now), low[now] = min(low[now], low[to]), ++son;
if (low[to] >= dfn[now]){
++scc_cnt;
yzh.add(now, scc_cnt + n);
yzh.add(scc_cnt + n, now);
do {
int u = stack[top--];
yzh.add(u, scc_cnt + n);
yzh.add(scc_cnt + n, u);
} while (stack[top + 1] != to);
}
} else if (to != fa) low[now] = min(low[now], dfn[to]);
}
if (!son && !fa) {
++scc_cnt;
yzh.add(now, scc_cnt + n);
yzh.add(scc_cnt + n, now);
}
}

注意到,有时候,我们需要根据题意记录树边的边权。就比如例题。

思考我们建出树的意义是什么——原仙人掌上,不好求两点间的距离,而在树上,我们可以轻松利用 LCA 等方法求得两点间的距离。所以树的边权应该和距离有关。

我们发现,仙人掌中,原来的环都变成了菊花。原图上的距离是环上的最短距离,而在树上是从花瓣到花心,再从花心到花瓣。这样似乎没看出些什么。我们不妨把比较特殊的 LCA 处放在最后讨论,接下来只考虑往上跳 LCA 的过程。

发现,只会存在花瓣到花心,再到花顶端的那个花瓣。我们不妨设花心到顶端花瓣的距离为 \(0\),而其余花瓣到花心的距离设置成原图环上到顶点的距离。这样就可以很好转化边权了。

至于 LCA 处,也很简单。如果 LCA 是圆点,那么不用处理;反之是方点,就要算其对应的原图上环的两点间的距离,这两点是来自询问两点不超过 LCA 的最浅祖先。这很好理解。

其实分析到这里,代码已经可以打出来了,但是在建树的时候注意如何优雅地处理边权,并精简代码。

那么再来模一模样例加深印象。

这里加粗的是原图的圆点,其他是方点。左图是圆仙人掌,右图是建出来的圆方树。

\(\operatorname{dist}(5, 7)\):由于 \(\operatorname{LCA}(5, 7) = 1\) 是圆点,所以直接路径和相加,即 \(\operatorname{dist}(5, 7) = 3 + 0 + 1 + 0 + 2 + 0 = 6\)。

\(\operatorname{dist}(4, 8)\):由于 \(\operatorname{LCA}(4, 8) = 14\) 是方点,所以要先跳到差一步到 \(14\) 的地方,即 \(4\) 和 \(3\),环上距离为 \(\min \lbrace 1, 4 - 1 \rbrace = 1\),\(\operatorname{dist}(4, 8) = 2 + 0 + 1 + 0 + 1 = 4\)。

代码

略去了快读快写,使用树剖求树上距离、LCA、和跳 father。

时间复杂度:\(\Theta(n + m + q \log n)\),瓶颈在树剖查询的 \(\log\)。

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std; struct Graph{
struct node{
int to, nxt, len;
} edge[1000010 << 1];
int eid = 1, head[20010];
inline void add(int u, int v, int w){
edge[++eid] = {v, head[u], w};
head[u] = eid;
}
inline node & operator [] (const int x){
return edge[x];
}
} xym, yzh; int n, m, q; int dfn[20010], low[20010], timer, cnt;
int dis[20010], re[20010];
int sum[20010], stack[20010], stop;
bool onleft[20010]; void tarjan(int now, int fr) {
dfn[now] = low[now] = ++timer, stack[++stop] = now;
for (int i = xym.head[now], to; to = xym[i].to, i; i = xym[i].nxt) if (i ^ fr ^ 1) {
if (!dfn[to]) {
dis[to] = dis[now] + xym[i].len;
re[to] = xym[i].len; // 如果这是一条非环边,那么把它看做 now -> to -> now 的环,所以这里要设置初值
tarjan(to, i), low[now] = min(low[now], low[to]);
if (low[to] >= dfn[now]) {
++cnt, sum[cnt] = re[stack[stop]] + dis[stack[stop]] - dis[now]; // 记录环的总长
yzh.add(cnt, now, 0), yzh.add(now, cnt, 0);
do {
int u = stack[stop--];
int len = min(dis[u] - dis[now], sum[cnt] - dis[u] + dis[now]);
onleft[u] = len == dis[u] - dis[now];
yzh.add(u, cnt, len);
yzh.add(cnt, u, len);
} while (stack[stop + 1] != to);
}
} else if (dfn[to] < dfn[now]) {
re[now] = xym[i].len; // 环的闭环那条边的长度
low[now] = min(low[now], dfn[to]);
}
}
} int siz[20010], top[20010], son[20010];
int line[20010], tfa[20010], dpt[20010]; void dfs(int now, int fa) {
tfa[now] = fa, siz[now] = 1, dpt[now] = dpt[fa] + 1;
for (int i = yzh.head[now], to; to = yzh[i].to, i; i = yzh[i].nxt) {
if (to == fa) continue;
dis[to] = dis[now] + yzh[i].len;
dfs(to, now), siz[now] += siz[to];
if (siz[to] > siz[son[now]]) son[now] = to;
}
} void redfs(int now, int tp) {
line[dfn[now] = ++timer] = now;
top[now] = tp;
if (son[now]) redfs(son[now], tp);
for (int i = yzh.head[now], to; to = yzh[i].to, i; i = yzh[i].nxt) {
if (to == tfa[now]) continue;
if (to == son[now]) continue;
redfs(to, to);
}
} inline int lca(int u, int v) {
while (top[u] != top[v]) {
if (dpt[top[u]] < dpt[top[v]]) swap(u, v);
u = tfa[top[u]];
}
if (dpt[u] < dpt[v]) swap(u, v);
return v;
} inline int jump(int now, int ed) {
int res = 0;
while (top[now] != top[ed])
res = top[now], now = tfa[top[now]];
return now == ed ? res : line[dfn[ed] + 1];
} inline int query(int u, int v) {
int p = lca(u, v);
if (p <= n)
return dis[u] + dis[v] - 2 * dis[p];
int fu = jump(u, p), fv = jump(v, p);
int d1 = dis[fu] - dis[p], d2 = dis[fv] - dis[p];
if (!onleft[fu]) d1 = sum[p] - d1;
if (!onleft[fv]) d2 = sum[p] - d2;
return dis[u] - dis[fu] + dis[v] - dis[fv] + min(abs(d1 - d2), sum[p] - abs(d1 - d2));
} signed main() {
fread(buf, 1, MAX, stdin);
read(n), read(m), read(q);
for (int i = 1, u, v, w; i <= m; ++i) {
read(u), read(v), read(w);
xym.add(u, v, w);
xym.add(v, u, w);
}
cnt = n;
for (int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i, 0);
timer = dis[1] = 0, dfs(1, 0), redfs(1, 1);
for (int i = 1, u, v; i <= q; ++i) {
read(u), read(v);
write(query(u, v)), putchar('\n');
}
fwrite(obuf, 1, o - obuf, stdout);
return 0;
}

后记

不用圆方树亦可解此题,但是要多一些讨论、不如圆方树的直观。据说圆方树能在一般无向图上使用?我太蒻了啊。

圆方树学习笔记 & 最短路 题解的更多相关文章

  1. 仙人掌&圆方树学习笔记

    仙人掌&圆方树学习笔记 1.仙人掌 圆方树用来干啥? --处理仙人掌的问题. 仙人掌是啥? (图片来自于\(BZOJ1023\)) --也就是任意一条边只会出现在一个环里面. 当然,如果你的图 ...

  2. 圆方树&广义圆方树[学习笔记]

    仙人掌 圆方树是用来解决仙人掌图的问题的,那什么是仙人掌图呢? 如图,不存在边同时属于多个环的无向连通图是一棵仙人掌 圆方树 定义 原先的仙人掌图,通过一些奇妙的方法,可以转化为一棵由圆点,方点和树边 ...

  3. CF487E Tourists + 圆方树学习笔记(圆方树+树剖+线段树+multiset)

    QWQ果然我已经什么都学不会的人了. 这个题目要求的是图上所有路径的点权和!QWQ(我只会树上啊!) 这个如果是好啊 这时候就需要 圆方树! 首先在介绍圆方树之前,我们先来一点简单的前置知识 首先,我 ...

  4. 【BZOJ】2125: 最短路 圆方树(静态仙人掌)

    [题意]给定带边权仙人掌图,Q次询问两点间最短距离.n,m,Q<=10000 [算法]圆方树处理仙人掌问题 [题解]树上的两点间最短路问题,常用倍增求LCA解决,考虑扩展到仙人掌图. 先对仙人掌 ...

  5. BZOJ3331 压力 (圆方树+树上差分)

    题意 略 题解 求路径上的割点. 然后就直接圆方树上差分 CODE #include <bits/stdc++.h> using namespace std; inline void rd ...

  6. 【CF487E】Tourists(圆方树)

    [CF487E]Tourists(圆方树) 题面 UOJ 题解 首先我们不考虑修改,再来想想这道题目. 我们既然要求的是最小值,那么,在经过一个点双的时候,走的一定是具有较小权值的那一侧. 所以说,我 ...

  7. uoj30【CF Round #278】Tourists(圆方树+树链剖分+可删除堆)

    - 学习了一波圆方树 学习了一波点分治 学习了一波可删除堆(巧用 ? STL) 传送门: Icefox_zhx 注意看代码看怎么构建圆方树的. tips:tips:tips:圆方树内存记得开两倍 CO ...

  8. Note -「圆方树」学习笔记

    目录 圆方树的定义 圆方树的构造 实现 细节 圆方树的运用 「BZOJ 3331」压力 「洛谷 P4320」道路相遇 「APIO 2018」「洛谷 P4630」铁人两项 「CF 487E」Touris ...

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

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

  10. 【BZOJ2125】最短路(仙人掌,圆方树)

    [BZOJ2125]最短路(仙人掌,圆方树) 题面 BZOJ 求仙人掌上两点间的最短路 题解 终于要构建圆方树啦 首先构建出圆方树,因为是仙人掌,和一般图可以稍微的不一样 直接\(tarjan\)缩点 ...

随机推荐

  1. 苹果手机 ios 系统如何升级为鸿蒙HarmonyOS

    用苹果手机的朋友们注意了 根据最新的可靠消息,苹果手机升级为HarmonyOS,教程如下: 第一步 手机电量充足的情况下,将苹果手机连接至WIFI无线网络. 第二步 ...... [下一页]

  2. 串口收发UART(Verilog HDL)

    UART(Universal Asynchronous Receiver Transmitter,通用异步收发器)是一种异步串行通信协议,主要用于计算机和嵌入式系统之间的数据交换. 实现UART通信的 ...

  3. mysql删除主键索引,删除索引语法

    mysql删除主键索引,删除索引语法 ### Incorrect table definition; there can be only one auto column and it must be ...

  4. json字符串忽略null,忽略字段,首字母大写等gson,jackson,fastJson实现demo,T data JSON.parseObject json转换

    json字符串忽略null,忽略字段,首字母大写等gson,jackson,fastJson实现demo package com.example.core.mydemo.json.vo; import ...

  5. Mac修改文件名的颜色

    文章目录 前言 文件类型 LSCOLORS介绍 颜色 如何设置LSCOLORS环境变量 前言 Mac中修改文件名颜色是通过LSCOLORS这个环境变量来控制的 文件类型 11种文件类型信息如下所示 序 ...

  6. Nginx+Fail2ban 实现同一ip在一分钟内连续三次请求同一接口并响应成功时进行封禁

    1. 安装 Fail2Ban 和 Nginx 如果尚未安装 Fail2Ban 和 Nginx,可以使用以下命令进行安装: # CentOS默认的仓库中可能不包含Nginx,所以需要添加EPEL(Ext ...

  7. 使用 nginx 共享文件

    1. 安装nginx 2. 在nginx的配置文件的server部分加上如下的配置: location /shared/ { autoindex on; autoindex_exact_size on ...

  8. vba--将excel单元格格式改为常规格式

    Sub 改格式() ActiveWorkbook.activesheet.Select For Each Rng In ActiveSheet.UsedRange With Rng .NumberFo ...

  9. Windows服务器安全检查

    为降低windows服务器系统的脆弱性,除了补丁及时更新,还建议加强系统账号的管理. 1.精简系统登录账号,最小化登录权限 检查方法:开始->运行->compmgmt.msc(计算机管理) ...

  10. ArkTS基础知识

    [习题]ArkTS基础知识 及格分85/ 满分100   判断题 1. 循环渲染ForEach可以从数据源中迭代获取数据,并为每个数组项创建相应的组件. 正确(True)错误(False) 回答正确 ...