逆后序-求强连通分量(Kosaraju算法)

强连通分量:
在有向图中,如果顶点v和w相互可达,则称这两个顶点之间强连通。一幅图中任意两点之间强连通则称这幅图为强连通图。有向图的极大强连通子图就是有向图的强连通分量
Kosaraju算法是求解有向图连通分量较简单的算法。它需要用到原图及其转置图(转置图即所有边的方向与原图一一对应且相反)来进行深度优先搜索。既然强连通是指顶点之间相互可达,那么我们只需要求出原图的连通分量(求解图的连通分量),然后在其转置图中再执行搜寻,原图和转置图对应的每一个连通分量的交集就是我们要求的强连通分量。
Kosarsju算法可分为两大步:
(1)它首先对原图整个图进行深度优先搜索,记录每个点的后序(h[++hcnt]=s表示从小到大(正序)排名第hcnt的节点是s),得到原图的逆后序遍历的顶点顺序
(2)然后在反图(原图各个边反向的图)中以刚才求出的逆后序的顶点顺序进行深度优先搜索,在搜索过程中进行标记每个节点的连通分量序号。
模板题:csp201509-4

  • 某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路。
    现在,大臣们帮国王拟了一个修高速公路的计划。看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能。如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对。
    国王想知道,在大臣们给他的计划中,有多少个便利城市对。输入格式  输入的第一行包含两个整数n, m,分别表示城市和单向高速公路的数量。
    接下来m行,每行两个整数a, b,表示城市a有一条单向的高速公路连向城市b。输出格式  输出一行,包含一个整数,表示便利城市对的数量。

    样例输入 
    5 5 
    1 2
    2 3 
    3 4 
    4 2 
    3 5 
    样例输出 
    3 
    
  • 样例说明 有3个便利城市对,它们分别是(2, 3), (2, 4), (3, 4),请注意(2, 3)和(3, 2)看成同一个便利城市对。
    评测用例规模与约定 前30%的评测用例满足1 ≤ n ≤ 100, 1 ≤ m ≤ 1000; 前60%的评测用例满足1 ≤ n ≤
    1000, 1 ≤ m ≤ 10000; 所有评测用例满足1 ≤ n ≤ 10000, 1 ≤ m ≤ 100000。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,scnt;
int a[100010],b[100010],head[10010],scc[10010],h[10010],sum[10010];
bool vis[10010];
struct edge{
 int to,w,next;
}e[100010];
int cnt=0;
void add(int u,int v,int w){
 e[++cnt].to =v;
 e[cnt].w =w;
 e[cnt].next =head[u];
 head[u]=cnt;
}
int hcnt=0;
void dfs(int s){
 vis[s]=1;
 for(int i=head[s];i;i=e[i].next ){
  int v=e[i].to ;
  if(!vis[v])dfs(v);
 }
 h[++hcnt]=s;//逆序数组
}
void dfs2(int s){
 scc[s]=scnt;
 for(int i=head[s];i;i=e[i].next ){
  int v=e[i].to ;
  if(!scc[v])dfs2(v);
 }
}
int main(){
 cin>>n>>m;
 memset(head,0,sizeof(head));
 for(int i=1;i<=m;i++){
  scanf("%d%d",&a[i],&b[i]);
  add(a[i],b[i],1);
 }
 for(int i=1;i<=n;i++){
  if(!vis[i])dfs(i);
 }
 cnt=0;
 memset(head,0,sizeof(head));
 for(int i=1;i<=m;i++) add(b[i],a[i],1);//建反图
 for(int i=n;i;i--){//倒序遍历
  if(!scc[h[i]]){//scc[i]数组用来记录节点i的连通分量的序号
   scnt++;//连通分量的序号
   dfs2(h[i]);
  }
 }
 for(int i=1;i<=n;i++)sum[scc[i]]++;//记录各个连通分量中节点的个数
 long long ans=0;
 for(int i=1;i<=scnt;i++)ans+=sum[i]*(sum[i]-1)/2;
 cout<<ans<<endl;
 return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值