有关强连通分量

定义

在有向图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;}

我觉得差不多了 嗯。

赶在十点之前交。

新年快乐。

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

时间: 2024-10-20 06:50:13

有关强连通分量的相关文章

Kosaraju算法解析: 求解图的强连通分量

1. 定义 连通分量:在无向图中,即为连通子图. 上图中,总共有四个连通分量.顶点A.B.C.D构成了一个连通分量,顶点E构成了一个连通分量,顶点F,G和H,I分别构成了两个连通分量. 强连通分量:有向图中,尽可能多的若干顶点组成的子图中,这些顶点都是相互可到达的,则这些顶点成为一个强连通分量. 上图中有三个强连通分量,分别是a.b.e以及f.g和c.d.h. 2. 连通分量的求解方法 对于一个无向图的连通分量,从连通分量的任意一个顶点开始,进行一次DFS,一定能遍历这个连通分量的所有顶点.所以

POJ 2186 Popular Cows 强连通分量模板

题意 强连通分量,找独立的块 强连通分量裸题 #include <cstdio> #include <cstdlib> #include <cstring> #include <string> #include <algorithm> #include <iostream> using namespace std; const int maxn = 50005; int n, m; struct Edge { int v, next;

USACO network of school 强连通分量

这个题的意思是有一个有向图, 每个顶点可以发送软件到与其相连的顶点上, 现在问1,至少发送给几个顶点能满足所有顶点都收到软件, 2:如果想让这个图变成强连通图,至少添几条边.  特例是给定的图是一个强连通图的话答案是1, 0. 一般情况下我们先将这个图的强连通分量求出来缩成一个点然后统计入度为0的点和出度为0的点的个数, 答案一就是入度为0的点的个数, 答案就是他们两个之间的最大值.代码如下: /* ID: m1500293 LANG: C++ PROG: schlnet */ #include

强连通分量(学习心得)

定义:有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通如果有向图G的每两个顶点都强连通,称G是一个强连通图.有向图的极大强连通子图,称为强连通分量. 求强连通分量: vector<int>pic[maxn]; int dfn[maxn],low[maxn],ans[maxn]; bool ins[maxn]; stack<int>st; int dind=0,block=

POJ 2186:Popular Cows(强连通分量)

[题目链接] http://poj.org/problem?id=2186 [题目大意] 给出一张有向图,问能被所有点到达的点的数量 [题解] 我们发现能成为答案的,只有拓扑序最后的SCC中的所有点, 那么我们从其中一个点开始沿反图dfs,如果能访问到全图, 则答案为其所在SCC的大小,否则为0. [代码] #include <cstdio> #include <algorithm> #include <vector> #include <cstring>

【学习整理】Tarjan:强连通分量+割点+割边

Tarjan求强连通分量 在一个有向图中,如果某两点间都有互相到达的路径,那么称中两个点强联通,如果任意两点都强联通,那么称这个图为强联通图:一个有向图的极大强联通子图称为强联通分量.   算法可以在 的时间内求出一个图的所有强联通分量. 表示进入结点 的时间 表示从 所能追溯到的栈中点的最早时间 如果某个点 已经在栈中则更新  否则对 进行回溯,并在回溯后更新  #include<iostream> #include<cstdlib> #include<cstdio>

【强连通分量】tarjan算法及kosaraju算法+例题

阅读前请确保自己知道强连通分量是什么,本文不做赘述. Tarjan算法 一.算法简介 Tarjan算法是一种由Robert Tarjan提出的求有向图强连通分量的时间复杂度为O(n)的算法. 首先我们要知道两个概念:时间戳(DFN),节点能追溯到的最早的栈中节点的时间戳(LOW).顾名思义,DFN就是在搜索中某一节点被遍历到的次序号(dfs_num),LOW就是某一节点在栈中能追溯到的最早的父亲节点的搜索次序号. Tarjan算法是基于深度优先搜索的算法.在搜索过程中把没有Tarjan过的点入栈

有向图的强连通分量(tarjan算法)

强连通分量 有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.有向图的极大强连通子图,称为强连通分量(strongly connected components). 考虑强连通分量C,设其中第一个被发现的点为x,则,C中其他的点都是x的后代.我们希望在x访问完成时立即输出C(可以同时记录C,输出代表

【BZOJ1051】1051: [HAOI2006]受欢迎的牛 tarjan求强连通分量+缩点

Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎. 这种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认为牛C受欢迎.你的任务是求出有多少头牛被所有的牛认为是受欢迎的. Input 第一行两个数N,M. 接下来M行,每行两个数A,B,意思是A认为B是受欢迎的(给出的信息有可能重复,即有可能出现多个A,B) Output 一个数,即有多少头牛被所有的牛认为是受欢迎的. Sample Input 3 3

POJ2186 Popular Cows 【强连通分量】+【Kosaraju】+【Tarjan】+【Garbow】

Popular Cows Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 23445   Accepted: 9605 Description Every cow's dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you are given up to M (1 <= M &l