targan求强连通分量

洛谷P1726

题目简述

在一个有向图中,寻找最大的连通分量并输出(如果有两个连通分量相同输出字典序小的)。

相关知识

连通
无向图中的两个点可相互到达,则这两个点是连通的。
有向图中的两个点可相互到达,则这两个点是强连通的。

连通图
无(有)向图中,所有的点都可相互到达。

连通分量
无向图中的双连通分量(点双连通分量,边双连通分量)是个子图,该子图中任意两个点可互相到达。
有向图中称为强连通分量(Strongly Connected Component),强连通分量是个子图,该子图中任意两个点可互相到达。

时间戳
在一次搜索过程中,第几个搜索到这个点,这个点的时间戳就为几。

相关变量

每个点有两个参数,dfn,low。
dfn表示该点的时间戳,low表示该点可以到达的所有点中最小的时间戳(其实并不是这样,后面有反例)。

思路

在dfs的过程中不断更新dfn,low这两个变量,并将访问到的点入栈(这个栈用来将不同的SCC分开,求无向图的连通分量不需要栈)。
具体操作:每到达一个点,先将该点入栈,然后尝试访问下一个点,
若下一个点没有被访问过,则dfs,
若下一个点被访问过且在当前栈里,则直接利用下一个点的dfn更新当前点的low。
dfs回溯的过程中也要更新low。

我们先看下在一个连通图中使用该算法是个什么情况。


我们从1开始,先将1入栈,此时1号点的dfn,low都为1,其它点为初始值0。

从1号点来到2号点,将2入栈并更新dfs与low。

从2来到3进行相同的操作。

从3试图访问1就要注意了,由于1已经在栈里,故不对1dfs,直接更新3的low,然后开始回溯。

从3回到2时,用3的low更新2的low(即low[2]=min(low[2],low[3])),这是因为3能到达的点2一定能到达。

最后回到1,从1开始的dfs结束,此时有\(low[1]==dfn[1]\),这意味着我们找到了一个连通分量1,2,3。
此时\(low[2]==low[3]==1\)。这说明2,3都能到达1,又因为这次dfs是从1开始,所以1也能到达2,3。
这就解释了为什么这些变量(dfn,low)能确定一个连通分量。
再看栈中的元素,2,3都位于1的上面,将1及以上的元素出栈即可得到对应的连通分量。
具体细节请看代码

参考代码

#include<bits/stdc++.h>
using namespace std;
const int N=5e3+10;
vector<int>g[N];//存图
int n,m;
int ans,f[N],cnt[N],Max;
//ans是连通分量的个数,f[i]表示i属于哪个连通分量,cnt[i]表示第i个连通分量的大小
//Max是最大连通分量的大小
int dfn[N],low[N],tim;
stack<int>st;//栈来存储连通分量
void targan(int x){
    st.push(x);//每到一个元素先入栈
    dfn[x]=low[x]=++tim;//更新dfn与low
    for(int i=0;i<g[x].size();++i){
        int to=g[x][i];
        //注意这里就不要加if(v==f)continue;
        if(!dfn[to])targan(to),
        low[x]=min(low[x],low[to]);//如果to没有被访问过,就dfs,返回的时候更新low
        else if(!f[to])low[x]=min(low[x],dfn[to]);//其实这里改成min(low[x],low[to])也没事,反正最终判的是low[x]==dfn[x]
        //如果to在当前的栈里面,说明to已经是x的祖先(从x可以到to),直接更新low
       //这里直接用f判断是否在栈里面了
    }
    if(low[x]==dfn[x]){//满足这个式子就说明包含x的连通分量已找到,且x是进入该联通分量的第一个节点
                       //根据low的定义,该连通分量所有点的low都是x,但部分点的low可能不是x
        ++ans;
        while(st.top()!=x){//去除该连通分量的所有点
            int now=st.top();
            f[now]=ans;
            cnt[ans]++;
            st.pop();
        }
        f[st.top()]=ans;
        cnt[ans]++;
        st.pop();
        Max=max(Max,cnt[ans]);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    int a,b,t;
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&a,&b,&t);
        g[a].push_back(b);
        if(t==2)
        g[b].push_back(a);
    }
    for(int i=1;i<=n;++i){
        if(!dfn[i])targan(i);
    }
    for(int i=1;i<=n;++i){
        if(cnt[f[i]]==Max){
            int a=f[i];
            printf("%d\n",cnt[a]);
            for(int j=i;j<=n;++j){
                if(f[j]==a)printf("%d ",j);
            }
            return 0;
        }
    }
    return 0;
}

关于low


我们可以发现4号点的low是2,而其它节点的low是1,虽然这不会影响最终的结果,
但这有点不符合low的定义。
将low改成:在深搜优先搜索树中当前节点及其子代能访问到的最早结点就行了。

注意这里求得的连通分量是极大连通分量。

上面这个图只有一个SCC,而不是两个。

posted @ 2022-02-13 21:55  何太狼  阅读(58)  评论(0编辑  收藏  举报