圆方树大概分两种,一个是圆方树,一个是广义圆方树。
圆方树
这可以解决仙人掌上的问题。
任意一条边至多只出现在一条简单回路的无向连通图称为仙人掌。
很多题解将其作为无向图构建,本文将其构建为外向树,在这个问题中两种构建方式不会影响求解。
构建方式
记读入的图为原图,构建的圆方树为新图。
首先,新图保留着原图的点集,这些点记为圆点。
将原图任意一个点(实现中指定 1 号点即可)作为根节点,然后在原图跑一遍 dfs。
每当找到一个环的时候(用 tarjan 算法),将进入环的点(也就是边双的根节点)记为头节点,然后在新图上对加一个方点,并让头节点向这个方点连边,同时,方点向其它点 连边。
下面使原图与对应新图(圆方树)的一个例子:
tarjan 算法变形
如何才能找到所有的简单环?为了方便,用边双连通分量求解。但是边双是不能轻易处理简单环的,比如这个图:

1,2,3,4,5,6,就是一个边双,但显然不是一个简单环。但是点双可以处理,至于为什么不用点双,可能是因为两者都可,而边双看起来简洁。如何改进边双:
原算法中,只要还在栈中,我们都将其看为一个边双联通分量。
但是,现在如果我们还遇到这种情况的话,就应该一个一个的处理为一个一个的简单环,而不是揉在一起。

比如这张图,黑色边组成一颗搜索树,当 去搜儿子 号点时,发现 号点被搜过,那么就可以从 号点开始倒推搜索树,一直到 ,这样就可以找出简单环。
all in all,看一下代码:
void build_circle(int x, int y, int z) {
for (int i = y; i != x; i = fat[i]) {
...
}
...
}
void tarjan(int u, int from) {
dfn[u] = low[u] = ++idx;
for (int p = first[u]; p; p = ed[p].next) {
int v = ed[p].e, w = ed[p].d;
if (!dfn[v]) {
fat[v] = u, fwt[v] = w, fet[v] = p;
tarjan(v, p);
low[u] = min(low[u], low[v]);
if (dfn[u] < low[v]) G[u].push_back({v, w});
}
else if (p != (from ^ 1)) low[u] = min(low[u], dfn[v]);
}
for (int p = first[u]; p; p = ed[p].next) {
int v = ed[p].e, w = ed[p].d;
if (dfn[u] < dfn[v] && fet[v] != p) build_circle(u, v, w); // 开始倒退成环
}
}
今天就到这里了。
782

被折叠的 条评论
为什么被折叠?



