图论填坑系列TAT

这篇文章禁止以任何形式转载。
这篇文章禁止以任何形式转载。
这篇文章禁止以任何形式转载。


dij我现在还不会我会说嘛……
虽然说暂时死不了。
Tarjan算法
首先我们要知道这个东西可以把有向图缩掉强连通分量后变成DAG。
有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。
简而言之,有向图的强连通分量就是环嘛。
我们考虑一个DAG的dfs搜索树。
对点进行标号,我们会发现,如果一个点可以访问到已经标号过的点,那么这个点显然可以回溯到某个子树的根节点。
让我们来冷静思考一下,其实就是这样:
这里写图片描述
发现一个问题:
1.紫色的边显然和这个节点不是双联通的。
2.蓝色的边才是。
啊……我数学不好,其实是两个问题hhh
发现,实际上我们紫色的边是横叉边,(管它怎么叫呢)即,虽然可以返回深度更浅的地方,但是实际上它并不是强联通的。
我们发现,只有指向已经压入dfs过程中的栈的点的这种边才是证明强联通的根据。
而且,不仅如此,我们还发现一些性质:
如果u是v的父亲,v可以返回v的某个父亲,那么u也必然能返回这个节点。
如果u是v的父亲,v可以返回u的某个祖先,那么u能返回的节点v也同样可以返回。
我们知道的一点是,如果某个节点x可以返回其祖先y,那么x和y是强联通的。
更进一步讲,如果某个节点x可以返回其祖先y,那么 y>x 这一段路径都是强联通的。
仔细想一想,我们如果想知道u向搜索树往下走可以返回的最浅祖先,那么我们就对其子树进行计算然后取标号最小的即可。
但是我们遇到一个问题。
这里写图片描述
如图所示的情况,我们这四个点显然是强连通的。
仔细思考后,我们知道每个强连通分量必然是有一个根的,我们可以看出的一点是,如果一个节点的可回溯祖先节点就是它本身的话,那么这个节点必然无法再向上回溯,即这个点单独属于一个强连通分量。
我们只需要在dfs的过程中记录如下信息即可:
1.dfs序及编号最早的回溯节点
2.属于哪个scc

void dfs(int x)
{
    dfn[stk[++ tp] = x] = low[x] = ++ tim;
    RepG(i,x)
    {
        if(!dfn[v])dfs(v),low[x] = min(low[v],low[x]);
        else if(!bel[v])low[x] = min(dfn[v],low[x]);
    }
    if(dfn[x] == low[x])
    {
        int y;++ scc;
        do{bel[y = stk[tp --]] = scc;}while(y != x);
    }
}

这个是有向图的强连通分量的知识。


接下来强调一个点双联通。
对于一个连通图,如果任意两点至少存在两条点不重复路径,则称这个图为点双连通的.(简称双连通)点双联通分量就是极大点双联通子图
想一想之后可以画出三元环什么的……
然后画出了一个”8”字形。
(就是上面一个三角底下一个三角共顶点)
请注意,这个图并不是点双联通,因为对于上下两个三角的点,只有一条经过中间点的路径T_T
所以说啊……
点双连通图的定义等价于任意两条边都同在一个简单环中。
感性认(民)识(科)一下,觉得就是对的吧……
不(会)证明了……
那么我们来想想,如果两个子图并不是点双联通的,有什么特别的事情么?
显然:这里写图片描述
对于这个和diep里面某些tank很像的图片,对三角形进行逆时针标号。
我们发现,中间的这个点:
三角形1<->三角形2要经过这个点。
三角形1<->三角形3要经过这个点。
三角形2<->三角形3要经过这个点。
真是痛苦呢,要被这么多人踩哈哈哈哈哈
我们定义割点:删去这个点及其伸出的边之后,原图是不联通的。
我们可以发现,只有中间的点是割点

================================================

注意,以下是求解割点部分,和点双联通分量虽有关系,但是暂时关系不大。
我们可以先忘掉之前点双联通分量的定义,专心来看怎么求解割点。
考虑无向图的dfs搜索树(也是一个点只遍历一遍),发现:
1.当根节点的子树数目>1的时候,根节点是割点。
2.对于非根节点u,如果存在一个节点v,使得v能返回的最早节点是u子树中的点(含u)
关于这个东西的证明:
1是显然的。
对于2:
这里写图片描述
我们删掉那个最大个的节点之后,会发现这个节点的最左侧的儿子不联通。
实际上也是显然的,对吧。

void dfs(int x,int fa = -1)
{
    dfn[x] = low[x] = ++ tim;
    int idx = 0;
    RepG(i,x)
    {
        if(!dfn[v])
        {
            idx ++;
            dfs(v,x);
            low[x] = min(low[v],low[x]);
            if(low[v] >= dfn[x])
                isc[x] = 1;
        }
        else if(dfn[v] < dfn[x] && v != fa)
            low[x] = min(low[x],dfn[v]);
    }
    if(idx == 1 && x == Root)isc[x] = 1;
}

==================================================

我们终于可以正式进入求解点双的部分了。
我们发现,割点会存在于多个点双中,但是边只会存在某个点双中。
记录点双的数目是bcc。
在dfs的过程中记录边是否入栈。
考虑到割点会被重复计入,我们额外记录数组表示bcc时间戳就好了。

void dfs(int x,int fa = -1)
{
    int idx = 0,y;
    dfn[x] = low[x] = ++ tim;
    RepG(i,x)
    {
        if(v == fa)continue;
        if(!dfn[v])
        {
            idx ++;
            stk[++ scnt] = i;
            dfs(x,v);
            low[x] = min(low[v],low[x]);
            if(low[v] >= dfn[x])
            {
                isc[x] = 1;
                bel[++ bcc].clear();
                do
                {
                    y = stk[tp --];
                    if(bccno[edge[y].a] != bcc)
                    {
                        bel[bcc].push_back(edge[y].a);
                        bccno[edge[y].a] = bcc;
                    }
                    if(bccno[edge[y].b] != bcc)
                    {
                        bel[bcc].push_back(edge[y].b);
                        bccno[edge[y].b] = bcc;
                    }
                }while(edge[y].a != x || edge[y].b != v);
            }
        }
        else if(dfn[v] < dfn[x])
        {
            low[x] = min(low[x],dfn[v]);
            stk[++ tp] = i;
        } 
    }
    if(x == Root && idx > 1)isc[x] = 1;
}

写的好蛋疼啊……
这样我们就可以知道有几个点双辣!

===========================================

桥:一条边称为桥(或者割边)当且仅当去掉该边之后的子图不连通。
我们仔细思考一下,就能明白,当存在一条边满足:
low[v]>dfn[u] 的时候我们就可以确定(u -> v)是桥了。
边双联通分量:
如果任意两点至少存在两条边不重复路径,则称该图为边双连通的。
边双连通的极大子图称为边双连通分量
如果有k个桥,那么显然有(k+1)个边双。
怎么求解边双呢?
自然是只要遇到一个dfn[x] == low[x]的地方,那么就把这坨东西全都缩到一个边双利就好辣!
注意重边的判断!

void dfs(int x,int fa = -1)
{
    low[x] = dfn[stk[++ tp] = x] = ++ tim;
    RepG(i,x)
    {
        if((i ^ 1) == fa)continue;
        if(!dfn[v])
        {
            dfs(v,i);
            low[x] = min(low[v],low[x]);
            if(dfn[v] == low[v])isb[i] = 1;
        }
        else if(!bel[v])low[x] = min(dfn[v],low[x]);
    }
    if(dfn[x] == low[x])
    {
        scc ++;
        int y ;
        do{y = stk[tp -- ];bel[y] = scc;}while(x != y);
    }
}

GTMD,tarjan这个大坑终于被我填了。。。。
接下来必须填网络流和费用流的坑……
拆点什么的思想现在完全没有……
需要好好♂培♂养♂一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值