在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通。
如果有向图G的每两个顶点都强连通,则称G是一个强连通图。
非强连通图有向图的极大强连通子图,成为强连通分量(strongly connected components)。
算法流程:
用dfs遍历G中的每个顶点,通dfn[i]表示dfs时达到顶点i的时间,low[i]表示到达根的时间。
dfs跑到u的时候。
low[u ]=dfn[u]=++time.
u 入栈,扫描一遍u 所能直接达到的顶点v,如果v没有被访问过那么先dfs遍历v,再回来的时候,low[u]=min(low[v],low[u]);如果v在栈里,那么 low[u]=min(low[u],dfn[v]);
扫描完所有的v以后,如果low[u]=dfn[u]时,栈里u以及以u为根的顶点全部出栈,且刚刚出栈的就是一个极大强连通分量。
hdu 1269 联通块的数量是否为1
#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int N=10005;
vector<int> q[N];
int vis[N],dfn[N],low[N],cnt;
int tim,stack[N],top;
void tarjan(int u)
{
stack[top++]=u;
vis[u]=1;
low[u]=dfn[u]=++tim;
for(int i=0;i<q[u].size();i++)
{
int v=q[u][i];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[v],low[u]);//孩子可以到,我也可以到
}
else if(vis[v])//在栈中,说明其实是祖先
low[u]=min(low[u],dfn[v]);//能到达的祖先,所以更新。
}
if(low[u]==dfn[u])
{
cnt++;
int v;
do
{
v=stack[--top];
vis[v]=0;
}while(u!=v);
}
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)&&n+m)
{
for(int i=0;i<10005;i++)
q[i].clear();
memset(vis,0,sizeof(vis));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
tim=top=cnt=0;
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
q[a].push_back(b);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
puts(cnt==1?"Yes":"No");
}
}
Tarjan算法相当于在一个有向图中找有向环,那么我们Tarjan算法中重要的作用就是缩点。缩点基于一种染色实现,我们在Dfs的过程中,尝试把属于同一个强连通分量的点都染成一个颜色,那么同一个颜色的点,就相当于一个点。
将一个有向带环图变成了一个有向无环图(DAG图)。很多算法要基于有向无环图才能进行的算法就需要使用Tarjan算法实现染色缩点,建一个DAG图然后再进行算法处理。
引入一个数组color[i]表示节点i的颜色,再引入一个数组stack[i]实现一个栈,然后在Dfs过程中每一次遇到点都将点入栈,在每一次遇到关键点的时候将栈内元素弹出,一直弹到栈顶元素是关键点的时候为止,对这些弹出来的元素进行染色即可。
hdu 2767
题意:这里有一个东西要你证明,就是有n个式子,用1到n标记,有m个关系,每个关系为a b 表示a推导出b,那么我们要这n个式子都是等价的最少还需要多少个关系。
至少加几条边让整个图变成强连通,我们根据已有的关系建图之后,强连通缩点,然后我们分别求叶子和根的数量,那么最多的那个就是我们要的答案,但是当缩点只有一个点的时候,答案是 0。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int N=20005;
vector<int> q[N];
int vis[N],dfn[N],low[N],stack[N],belong[N];
int top,tim,cnt;
int in[N],out[N];
#define pb push_back
void intt()
{
memset(vis,0,sizeof(vis));
memset(dfn,0,sizeof(dfn));
for(int i=0;i<N;i++) q[i].clear();
top=tim=cnt=0;
}
void tarjan(int u)
{
stack[top++]=u;
low[u]=dfn[u]=++tim;
vis[u]=1;
for(auto v:q[u])
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]) low[u]=min(low[u],dfn[v]);
if(low[u]==dfn[u])
{
cnt++;
int v;
do
{
v=stack[--top];
vis[v]=0;
belong[v]=cnt;
}while(v!=u);
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m;
intt();
scanf("%d%d",&n,&m);
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
q[a].pb(b);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
if(cnt==1)
puts("0");
else
{
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
for(int i=1;i<=n;i++)
for(auto v:q[i])
if(belong[i]!=belong[v])
out[belong[i]]++,in[belong[v]]++;
int root=0,leaf=0;
for(int i=1;i<=cnt;i++)
{
if(in[i]==0)root++;
if(out[i]==0)leaf++;
}
printf("%d\n",leaf>root?leaf:root);
}
}
}
poj 2186
N头牛,M个关系,每个关系为 a b,表示牛a认为牛b 收欢迎,那么问根据所给信息判断有多少头牛是收到所有的牛的欢迎,而且这里a认为b受欢迎,b认为c受欢迎,那么a也会认为c受欢迎。
我们只要求叶子的个数,因为别的缩点都指向叶子,叶子处在最高层,就是最受欢迎的,那么我们根据所有缩点的out[i]=0的个数,判断,如果只有一个,那么这个缩点(强连通分量)的牛都是最受欢迎的,(这里注意,叶子数为0就是表示只有一个强连通分量,那么所有的牛都是最受欢迎的),如果有多个就输出0。
(吐槽,poj竟然不支持C++11
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
const int N=10005;
using namespace std;
vector<int> q[N];
int dfn[N]={0},vis[N]={0},low[N]={0},stack[N]={0},bel[N]={0},out[N]={0},in[N]={0};
int cnt,tim,top;
vector<int> qu[N];
void tanjar(int u)
{
stack[top++]=u;
vis[u]=1;
low[u]=dfn[u]=++tim;
for(int i=0;i<q[u].size();i++)
{
int v=q[u][i];
if(!dfn[v])
{
tanjar(v);
low[u]=min(low[v],low[u]);
}
else if(vis[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
cnt++;
int v;
do
{
v=stack[--top];
vis[v]=0;
qu[cnt].push_back(v);
bel[v]=cnt;
}while(v!=u);
}
}
int main()
{
int n,m;scanf("%d%d",&n,&m);
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
q[a].push_back(b);
}
tim=cnt=top=0;
for(int i=1;i<=n;i++)
if(!dfn[i])
tanjar(i);
if(cnt==1)
printf("%d\n",n);
else
{
for(int i=1;i<=n;i++)
for(int j=0;j<q[i].size();j++)
{
int v=q[i][j];
if(bel[i]!=bel[v])
out[bel[i]]++;
}
int leaf=0,leaf_num=0;
for(int i=1;i<=cnt;i++)
if(out[i]==0)
leaf++,leaf_num=i;
if(leaf!=1) puts("0");
else
printf("%d\n",(int)qu[leaf_num].size());
}
}
割点:一个结点称为割点(或者割顶)当且仅当去掉该节点极其相关的边之后的子图不连通。
桥:一条边称为桥(或者割边)当且仅当去掉该边之后的子图不连通。
在无向连通图G中,
1、根结点u为割点当且仅当它有两个或者多个子结点;
2、非根结点u为割点当且仅当u存在结点v,使得v极其所有后代都没有反向边可以连回u的祖先(u不算)