题目:
http://118.190.20.162/view.page?gpid=T29
算法:
Tarjan算法,这是讲的还不错的一篇:https://blog.csdn.net/qq_34374664/article/details/77488976
总的来说,就是将强连通分量当做一棵树去处理,到最后能找出这棵树的根节点,就找到一条强连通分量。
对low[]可以这样理解:
LOW[ i ] : 为i或i的子树能够追溯到的最早的栈中节点的次序号
细节:
记得把每个节点都走一遍,防止有漏掉的。
代码:
#include<bits/stdc++.h>
using namespace std;
const int MAX = 10005; //自己设定MAX最合适
vector<int>graph[MAX]; //存储有向图
//index[i]表示i是第几个被访问的结点,lowLink[i]表示从i出发经有向边可到达的所有结点最小的index,sccno[i]表示i所属的强连通分量的编号
int index[MAX], lowLink[MAX],sccno[MAX],dfsNo = 0,scc_cnt = 0;
int ans = 0; //最终结果
stack<int>s;
//基于DFS的tarjan算法
void DFS(int v){
index[v] = lowLink[v] = ++dfsNo;
s.push(v);
for(int i:graph[v]){
if(index[i]==0){
DFS(i);
lowLink[v] = min(lowLink[i],lowLink[v]);
}else if(sccno[i]==0){ //已被访问过但i还未出栈
lowLink[v] = min(lowLink[v],index[i]);
}
}
if(lowLink[v]==index[v]){
//是一个强连通分支的根节点
++scc_cnt; //强连通分支的编号
int t, num = 0; //num表示该强连通分支的节点的个数
do{
t = s.top();
s.pop();
++num;
sccno[t] = scc_cnt;
} while(t!=v) //当t==v,栈就到头了
ans += (num-1)*num/2; //加上该强连通分支的便利城市对个数
}
int main(){
int n,m,k,a,b;
scanf("%d%d",&n,&m); //不需要空格
while(m--){
scanf("%d%d",&a,&b);
graph[a].push_back(b);
}
for(int i = 1;i<=n;++i){
//为了防止有漏网之鱼,就从把每个结点都搞一遍,如果这个结点已经被搞过一遍了,即index[i]!=0
if(index[i]==0){
DFS(i);
}
}
printf("%d",ans);
return 0;
}