最近比赛时做一道仙人掌的题就因为tarjan打错而WA0了,非常伤心,突然发现自己脑海中的tarjan真是一坨shit(我无意冒犯tarjan大神),所以去找了图论专题的PPT复习了一波。
dfn,low的含义在这里就不解释了。
有向图的tarjan:
有向图的tarjan只有一种用途,就是缩强联通分量。
2-SAT问题中也可以判合法性。
在有向图tarjan中,会遇到三种边:
1.树边
2.返祖边
3.横插边
横插边要无视掉。
当dfn[x] == low[x],的时候,就把栈里x及x以上的退栈,缩成一个点。
代码:
void dg(int x) {
bd[x] = 1; d[++ d[0]] = x;
dfn[x] = low[x] = ++ td;
for(int i = final[x]; i; i = next[i]) {
int y = to[i];
if(!dfn[y]) dg(y), low[x] = min(low[x], low[y]); else
if(bd[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x]) {
for(; d[d[0]] != x; d[0] --)
ff[d[d[0]]] = x, bd[d[d[0]]] = 0;
d[0] --; ff[x] = x; bd[x] = 0;
}
}
无向图tarjan:
无向图没有横插边。
第一种:
缩边双连通分量。
无向图的边双联通分量其实类似于有向图的强联通分量。
所以这个做法也差不多,只是要标记一下走过来的边是哪条,不能直接走回去。
这个的话一开始把边集数组的计数变量tot赋值为1,就可以用^1来判断。
代码:
void tar(int x, int la) {
bd[x] = 1; d[++ d[0]] = x;
low[x] = dfn[x] = ++ td;
for(int i = final[x]; i; i = next[i]) if(i != (la ^ 1)){
int y = to[i];
if(!dfn[y]) tar(y, i), low[x] = min(low[x], low[y]); else
if(bd[y]) low[x] = min(low[x], dfn[y]);
}
if(dfn[x] == low[x]) {
tz ++;
while(d[d[0]] != x)
to[d[d[0]]] = tz, bd[d[d[0] --]] = 0;
to[d[d[0]]] = tz, bd[d[d[0] --]] = 0;
}
}
第二种:
这种的作用是搞仙人掌图。
仙人掌图标志:任意一条边最多存在与一个简单环中
大概是dfs一下,把环上点的父亲设为环顶(第一次进入环的位置),环顶也可以视作在一个环上,它的父亲设为那个环顶的父亲。
这样缩了以后,处理一些信息(例如最短路)一般只用在lca处特判一下从哪边绕过去。
还是先求出low和dfn。
在仙人掌图中,一个点x,y是x的一个子节点。
如果low[y]>=dfn[x],则说明y上不去,则y的子图就被x隔开了,可以视x为环顶,fa[y]=x.
不然的话y上得去,fa[y] = fa[x]。
找桥点的话则不同,x只要有一个子节点y满足low[y]>=dfn[x],x就是一个桥点。
代码:
void tar(int x, int la) {
low[x] = dfn[x] = ++ td;
for(int i = final[x]; i; i = next[i]) if(i != (la ^ 1)){
int y = to[i];
if(!dfn[y]) tar(y, i), low[x] = min(low[x], low[y]); else
low[x] = min(low[x], dfn[y]);
}
}
void tq(int x) {
bz[x] = 1;
for(int i = final[x]; i; i = next[i]) {
int y = to[i]; if(bz[y]) continue;
if(low[y] >= dfn[x]) fa[y] = x; else fa[y] = fa[x];
tq(y);
}
bz[x] = 0;
}
第三种:
这个用来缩点双连通分量。
我们知道割点会存在于多个点双连通分量,所以缩点的话要新开点。
和边双不同的是当前的x不要退掉。