Tarjan的各种操作

最近比赛时做一道仙人掌的题就因为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不要退掉。

猜你喜欢

转载自blog.csdn.net/cold_chair/article/details/79918157