有向图的强连通分量 hdu3836

今天做题发现图论的有向图强连通分量的tarjan算法不会,于是就照着这个博客https://www.byvoid.com/blog/scc-tarjan/学习了一下,感觉非常棒
题目是说等价证明,a,b,c,d互相证明,最少增加的推导数目,a能推出b,b能推出c,c能推出d,d能推出a,最少是4次。首先找出强连通分量,然后把每个强连通分量缩成一个点,得到一个DAG。接下来,设有a个节点(别忘了,这里的每个节点相对于原图的一个强连通分量)的入度为0,b个节点的出度为0,则max{a,b}就是答案。注意特殊情况:当原图已经强连通时,答案是0,而不是1.
定义lowlink(u)为节点u搜索的次序编号(时间戳),pre(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。由定义可以得出,
当lowlink(u)=pre(u)时,以u为根的搜索子树上所有节点是一个强连通分量

#include <iostream>
#include <vector>
#include <stack>
#include <stdio.h>
#include <string.h>
#define maxn 50005

using namespace std;

vector<int>G[maxn];
int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt;
//scc_cnt为GCC计数器,sccno[i]为i所在的SCC编号
stack<int> S;
void dfs(int u)
{
    pre[u]=lowlink[u]=++dfs_clock;//这里是用来标记这个点处理过,下次dfs的时候就可以跳过这个点了
    S.push(u);//每搜索到一个点,就压入栈中
    for(int i=0; i<G[u].size(); i++)//遍历与u相连的点
    {
        int v=G[u][i];
        if(!pre[v])//如果这个点没有遍历过(不在栈中),那么就对这个点深搜
        {
            dfs(v);
            lowlink[u]=min(lowlink[u],lowlink[v]);
        }
        else if(!sccno[v])//(在栈中)
        {
            lowlink[u]=min(lowlink[u],pre[v]);
        }
    }
    if(lowlink[u]==pre[u])//发现一个根
    {
        scc_cnt++;
        while(1)
        {
            int x=S.top();//此点以上的栈里面的点全出栈成为一个强连通分量
            S.pop();
            sccno[x]=scc_cnt;
            if(x==u)
                break;
        }
    }
}

void find_scc(int n)
{
    dfs_clock=scc_cnt=0;
    memset(sccno,0,sizeof(sccno));
    memset(pre,0,sizeof(pre));
    for(int i=0; i<n; i++)
        if(!pre[i])
            dfs(i);
}
//以上是模板
int in0[maxn],out0[maxn];
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0; i<n; i++)
            G[i].clear();
        for(int i=0; i<m; i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            u--;
            v--;
            G[u].push_back(v);
        }
        find_scc(n);
        for(int i=1; i<=scc_cnt; i++)
            in0[i]=out0[i]=1;//把每个强连通分量的缩点附成1 
        for(int u=0; u<n; u++)
            for(int i=0; i<G[u].size(); i++)
            {
                int v=G[u][i];
                if(sccno[u]!=sccno[v])//如果这两个不是一堆,那么他们中间肯定有连线,那么这两对的入度出度就不为0,因为我们要求的就是为0的,所以这块要处理一下
                    in0[sccno[v]]=out0[sccno[u]]=0;//这里处理成0为了和是1的相对照
            }
        int a=0,b=0;
        for(int i=1; i<=scc_cnt; i++)
        {
            if(in0[i])//为1的时候表示需要去抵消为0的情况,
                a++;
            if(out0[i])
                b++;
        }
        int ans=max(a,b);//最大抵消即为需要加的最少的边
        if(scc_cnt==1)
            ans=0;
        printf("%d\n",ans);
    }
    return 0;
}

你可能感兴趣的:(算法,博客,C语言)