图论 —— 图的连通性 —— Tarjan 缩点

缩点常应用于给一个有向图,求在图中最少要加多少条边能使得该图变成一个强连通图

首先求出该图的各个强连通分量,然后把每个强连通分量看出一个点(即缩点),最后得到了一个有向无环图(DAG)

对于一个DAG,需要添加 max(a,b) 条边才能使其强连通

其中 a 为 DAG 中出度为 0 的点总数,b 为 DAG 中入度为 0 的点总数

int n,m;
vector<int> G[N];
stack<int> S;
int dfn[N],low[N];
bool vis[N];//标记数组
int sccno[N];//记录结点i属于哪个强连通分量
bool in[N],out[N];//记录入度、出度是否为0
int block_cnt;//时间戳
int sig;//记录强连通分量个数
void Tarjan(int x){
    vis[x]=true;
    dfn[x]=low[x]=++block_cnt;//每找到一个新点,纪录当前节点的时间戳
    S.push(x);//当前结点入栈

    for(int i=0;i<G[x].size();i++){//遍历整个栈
        int y=G[x][i];//当前结点的下一结点
        if(vis[y]==false){//若未被访问过
            Tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(!sccno[y])//若已被访问过,且不属于任何一个连通分量
            low[x]=min(low[x],dfn[y]);
    }

    if(dfn[x]==low[x]){//满足强连通分量要求
        sig++;//记录强连通分量个数
        while(true){//记录元素属于第几个强连通分量
            int temp=S.top();
            S.pop();
            sccno[temp]=sig;
            if(temp==x)
                break;
        }
    }
}
void shrink(){//缩点
    memset(in,false,sizeof(in));
    memset(out,false,sizeof(out));
    for(int i=1;i<=sig;i++){//对于所有的强连通分量,将其入度、出度均视为1
        in[i]=true;
        out[i]=true;
    }

    for(int x=0;x<n;x++){//枚举n个点
        for(int i=0;i<G[x].size();i++){//对第x个点的每个后继节点
            int y=G[x][i];
            if(sccno[x]!=sccno[y]){//统计每个点出度、入度是否为0
                out[sccno[x]]=false;
                in[sccno[y]]=false;
            }
        }
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
            G[i].clear();
        while(m--){
            int x,y;
            scanf("%d%d",&x,&y);
            x--;
            y--;
            G[x].push_back(y);
        }

        sig=0;
        block_cnt=0;
        memset(vis,false,sizeof(vis));
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        memset(sccno,0,sizeof(sccno));

        //Tarjan求强连通分量
        for(int i=0;i<n;i++)
            if(vis[i]==false)
                Tarjan(i);

        shrink();//缩点
        int a=0,b=0;
        for(int i=1;i<=sig;i++){//统计入度、出度为0的点的个数
            if(in[i])
                a++;
            if(out[i])
                b++;
        }
        int res=max(a,b);
        if(sig==1)//强连通分量为1时
            res=0;

        printf("%d\n",res);
    }
}
posted @   老程序员111  阅读(56)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
点击右上角即可分享
微信分享提示