同步发表于 P5236 题解。
0. 前置知识
本蒟蒻也是最近学习这个 巨毒瘤 的算法,好多地方也是一头雾水,仔细想了几个晚自习后,有些恍然大悟,于是写了这篇题解。
需要的东西:Tarjan(似乎没有具体的模板)。
就是一个求边强连通分量的算法,一会我们要利用它并改进为我们所用。
还有一颗看完我的博客的心~~
1. 圆方树
其实,圆方树就是将环的作用转化为一棵树的作用,使原来的图变为了新图,许多性质没有变化,但处理树会简单许多。
以例题为例:题目传送门
想一想,怎样将环变为树的样子?
回归定义的一个特殊性质:
任意一条边至多只出现在一条简单回路的无向连通图称为仙人掌。
翻译成人话(?),就是原图由一些边不相交的环和另外的边构成。
首先,可以想到的是,将一个环缩为一个点,将整个图缩为一棵树(因为无向图中只有树和环两种形状,没有第三种,又因为没有环套环(边重合),所以一定变为一棵树)。
但是,环内的边和点间的距离就没法统计了。
我们假设任意一个点为根,那么一定会有一个点距离根节点最近。
来看一个图( 盗的例题的图 )
假设七号点为根,那么,对于 “1-2-3-4” 的环,三号点距离根节点最近。
我们设最近的点具有 “A” 性质。
下面,我们证明一个东西:环内的点到根节点一定经过环内具有 “A” 性质的点。
显然,如果不经过该点的话,就不可能达到根节点。
那么,如果在树中,该点如果想向上走的话,必须经过该环的 “A” 节点。
我们就可以考虑将这种关系转化为树的节点之间的关系。
环内的节点通向 “A” 节点只可能有两条路径:顺时针和逆时针。
又由于对于每一个点,长的一条路肯定用不上,那么我们只需要存到 “A” 节点的最短距离即可。
那么,原图很大程度上就等价于新图了。
但是,对于一些题来说,我们需要判断原来的边还是环中的边变换过来的。
那么,我们可以使用 “圆方树”。
假设原来的点叫做圆点,新建的点叫做方点。
对于环内的节点,我们可以新建一个方点,向 “A” 的点连接一条权值为 0 的边,在从新点到其他点连接原来应有的权值。
现在,我们判断是不是原来的边,只需判断是不是有新建的点即可。
举个例子,上面的仙人掌建为圆方树(7 号是根节点)是:
2. Tarjan 算法及变形
我们刚才讨论的范围,是在能求简单环的基础上,现在,我们的问题是,如何才能找到所有的简单环?
可能大家都会想到,直接用 Tarjan 算法,就可以去求了。
但是,有一个问题:Tarjan 求的是边双联通分量,即去掉任意一条边后,原图仍然联通,那么,原图的 “123456”6 个点,满足该要求,但他们并不属于同一个简单环,怎么办呢?
于是,需要我们改进该算法。
首先,我们回顾一下原算法。
1 | //来自本人缩点模板题 |
原算法中,只要还在栈中,我们都将所有归为一个边双联通分量。
但是,现在,如果我们还遇到这种情况的话,就应该一个一个的处理为一个一个的简单环,而不是揉在一起。
所以,当我们遇到 $low[e[i]]<dfn[x]$ 的时候,我们直接倒回去,就可以倒推出一整个环的情况了。
请注意,此时 $x$ 也为其中的点。
具体来说,我们遇到这种情况时,直接 $\operatorname{build-round-square}(x,e[i],w)$,表示从 $e[i]$ 倒推,直到 $x$ 为 “A” 点,其中 $(x,e[i])$ 的权值为 $w$。
具体来说,我们遇到了这样的情况:
(好像少标了一条边的权值)
我们知道,Tarjan 算法建了一棵搜索树,在树上进行计算。
如图,黑边就是搜索树的边,而红边就是非树边。
肉眼可见,有两个环。
对于每个节点,我们维护他在搜索树中的父亲,还有到父亲的距离。
如果相连的节点在他的下面(即不是父亲),并且他的父亲不是该节点,说明有另外一条路径(树边)可达他的儿子,我们就可以 $\operatorname{build-round-square}(x,e[i],w)$ 了。
举个例子,搜索完 1 节点后,我们找连接点,找到 5 号点,发现满足上面的性质,于是就 $\operatorname{build-round-square}(1,5,2)$ 即可以了。
如果你还感到费解,请看前面的例图,加以理解。
如果你对传参有不理解,看一下代码就知道了。
于是,我们就可以得到这样的代码:
1 | void add(int h[],int a,int b,int c) |
3. 回归本题
1)题意
- 给定一个仙人掌图,有 $Q$ 次询问,询问 $u$ 和 $v$ 之间的最短路。
- $n,q\leq10^4,m\leq2*10^4,w\leq10^5$。
2)具体算法
前面的东西是总体的仙人掌转圆方树的算法,对于不同的题来说,肯定也会有一些细节不同。
当然,仙人掌的题的大概思路是:
- 将仙人掌转化为圆方树。
- 结合其他树的算法(树链剖分,点分治等),将本题树的写法写好。
- 分情况,看是圆点还是方点,进行算法调整。
本题也是如此。
考虑树上怎么做。
很明显,使用倍增算法,将一个节点的 $2^k$ 次祖先存储下来。
再利用前缀和的思想,答案即为:$ans=d[a]+d[b]-2d[lca]$。
现在,我们考虑分类讨论。
首先,假设 $lca$ 是圆点,直接按上面求即可(因为会在原来的点相会)。
假设 $lca$ 是方点呢?
这说明,当到了 $lca$ 的前一层时,两个点是在同一个环上。
对于每一个节点,可以存储一个前缀和 $s[]$,即从 “A” 性质的点按同一个方向(指一个环中是同一个方向)走到该点的距离。
特别地,”A” 性质的点记为环的总权值。
现在我们可以将环间的路径表示为($x,y$ 分别表示 $a,b$ 环里的节点):$d=\min(|s[x]-s[y]|,stot[x]-|tot - |s[x] - s[y]||)$。
现在我们就可以完美的解决了。
4. 代码
还会有一些细节从代码中呈现。
1 |
|