码迷,mamicode.com
首页 > 其他好文 > 详细

有关强连通分量

时间:2020-01-21 23:13:29      阅读:96      评论:0      收藏:0      [点我收藏+]

标签:强连通图   基于   break   map   tar   mem   nsa   模板   nts   

定义

有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。——以上来自百度百科技术图片

如上图,强连通分量有{1,2,3,4},{5},{6}。


 

Tarjan 算法

Tarjan算法基于有向图的深度优先遍历,能够在线性时间内求出一张图的各个强连通分量。

基本思路是对于每个点,找到与它一起能构成环的节点。一个环一定是强连通分量。

记录一个时间戳dfn[x],在深度优先遍历时,按每个节点第一次被访问的时间顺序依次标记。

维护一个栈。

x的追溯值low[x]记录x在栈中且存在一条从以x为根的子树出发以该点为终点的节点的最小时间戳。

当节点x第一次被访问时,将x入栈,初始化low[x]=dfn[x]。

找x的每一条出边y,如果y没被访问,则递归访问y,从y回溯后,令low[x]=min(low[x],low[y]);如果y被访问,且在栈中,则令low[x]=min(low[x],dfn[y])。

在x回溯之前,判断low[x]是否等于dfn[x],如果是,则不断弹出栈中元素,直至x被弹出。

此时弹出的所有节点,便构成了一个强连通分量,用一个vector型的scc[i]数组保存。

c[x]数组表示x所在的强连通分量的编号。

ins[x]数组记录x点是否入栈。

void tarjan(int x)
{
    dfn[x]=low[x]=++num;
    stack[++top]=x;
    ins[x]=1;
    for(int i=head[x];i;i=next[i])
    {
        int y=ver[i];
        if(!dfn[y])
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(ins[y])
        low[x]=min(low[x],dfn[y]);
        if(dfn[x]==low[x])
        {
            cnt++;int k;
            do
            {
                k=stack[top--];
                ins[k]=0;
                c[k]=cnt;
                scc[cnt].push_back(k);
            }
            while(x!=k)
        }
    }
} 
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);

缩点

将有向图中的每个强连通分量缩成一个点。

对于每个强连通分量,在它们之间连一条边,得到一个有向无环图,保存在另一个邻接表中。

for(int x=1;x<=n;x++)
{
    for(int i=head[x];i;i=next[i])
    {
        int y=ver[i];
        if(c[x]==c[y]) continue;
        add(c[x],c[y]);//保存在邻接表中 
    }
}

Kosaraju 算法

首先有对于一张有向图,它的原图和反图的强连通分量是一样的。

利用此原理,Kosaraju算法先对原图做dfs,记录各点退出dfs的时间顺序

求反图。

对反图再次进行dfs,按照第一次保存的顺序由大到小访问顶点。

void dfs1(int x)
{
    vis[x]=1;
    for(int i=0;i<g[x].size();i++)
{
        if(vis[g[x][i]])
        dfs1(g[x][i]);  
}   
vs.push_back(x);
}

void dfs2(int x,int y)
{
    vis[x]=y;
    cmp[x]=y;
    for(int i=0;i<rg[x].size();i++)
{
        if(!vis[g2[x][i]])
    dfs2(g2[x][i],y);
    }
}
int scc()
{
    memset(vis,0,sizeof(vis));
    vs.clear();
    for(int i=0;i<V;i++)
{
        if(!vis[i])
   dfs1(i);
   }
    memset(vis,0,sizeof(vis));
    int k=0; 
   for(int i=vs.size()-1;i>=0;i--)
{
        if(!vis[vs[i]])
   dfs2(vs[i],k++);
    }
    return k;
}

一些例题

受欢迎的牛

https://www.luogu.com.cn/problem/P2341

模板题,用tarjan算法或者Kosaraju算法都可以。

真的很模板,就不贴代码了。

稳定婚姻

https://www.luogu.com.cn/problem/P1407

啧,不得不吐槽一下,这道题三观真不正。

建图,夫妻之间由man指向woman,情人之间由woman指向man(反过来也行)。

再判断强联通分量,如果夫妻之间在同一强连通分量中,就说明婚姻是不安全的,不在就是安全的。

#include<bits/stdc++.h>
using namespace std;

const int N=300002;
map<string,int> a;
int ver[N],next[N],head[N];
int dfn[N],low[N],s[N],ins[N],c[N];
int tot,cnt,idx,top;

void add(int x,int y)
{
    ver[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
}

void tarjan(int x)
{
    dfn[x]=low[x]=++idx;
    s[++top]=x;
    ins[x]=1;
    for(int i=head[x];i;i=next[i])
    {
        int y=ver[i];
        if(dfn[y]==0)
        {
            tarjan(y);
            low[x]=min(low[y],low[x]);
        }
        else if(ins[y])
        low[x]=min(low[x],dfn[y]);
    }
    if(dfn[x]==low[x])
    {
        cnt++;
        while(1)
        {
            c[s[top]]=cnt;
            ins[s[top]]=0;
            if(s[top--]==x) break;
        }
    }
}

int main()
{
    int n,m ;
    scanf("%d",&n);
    string s1,s2;
    for(int i=1;i<=n ;i++)
    {
        cin>>s1>>s2;
        a[s1]=i;
        a[s2]=i+n;
        add(i,i+n);
    }
    scanf("%d",&m ); 
    for(int i=1;i<=m ;i++)
    {
        cin>>s1>>s2;
        add(a[s2],a[s1]);
    }
    for(int i=1;i<=n*2;i++)
    {
        if(dfn[i]==0)
        tarjan(i);
    }
    for(int i=1;i<=n;i++)
    {
        if(c[i]==c[i+n]) puts("Unsafe");
        else puts("Safe"); 
    }
    return 0;
}

HXY烧情侣

https://www.luogu.com.cn/problem/P2194

怎么最近做的题都奇奇怪怪的[不解]。

求强连通分量就行了,也挺模板的一道题。

#include<bits/stdc++.h>
using namespace std;
const int N=300005;
int n,w[N],m,ans1,ans2;
int ver[N],next[N],head[N],tot;

void add(int x,int y)
{
    ver[++tot]=y;
next[tot]=head[x];
head[x]=tot;
}

const int mod=1e9+7;

int dfn[N],low[N],num,belong[N],all[N],cnt;
bool vis[N];
stack<int>s;
vector<int>G[N];
void tarjan(int x)
{
    dfn[x]=low[x]=++num;
    s.push(x);
vis[u]=1;
    for(int i=head[x];i;i=next[i])
{
        int y=ver[i];
        if(!dfn[y])
{
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
else if(vis[y])
low[x]=min(low[x],dfn[y]);
    }
    if(low[x]==dfn[y])
{
        int k=x;
++cnt;
        do{
            v=s.top();s.pop();
            vis[k]=c[k]=cnt;all[cnt]++;
            G[cnt].push_back(k);
        }while(k!=x);
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    scanf("%d",&m);
    for(int a,b,i=1;i<=m;i++)
{
        scanf("%d%d",&a,&b);
        add(a,b);
    }
    for(int i=1;i<=n;i++)
    if(!dfn[i]) tarjan(i);
    ans2=1;
    for(int i=1;i<=cnt;i++)
{
        int l=G[i].size(),k=0,minn=999999999;
        for(int j=0;j<l;j++)
{
            if(w[G[i][j]]<minn)
{
                minn=w[G[i][j]];
                k=1;
            }
else if(minn==w[G[i][j]] k++;
        }
        ans1+=minn;
        ans2=(ans2%mod*k%mod)%mod;
    }
    printf("%d %d",ans1,ans2);
    return 0;
}

我觉得差不多了 嗯。

赶在十点之前交。

新年快乐。

有关强连通分量

标签:强连通图   基于   break   map   tar   mem   nsa   模板   nts   

原文地址:https://www.cnblogs.com/mgtnb/p/12227131.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!