首先了解以下名词:
强连通:两个顶点a、b,若a有路径通向b、b亦有路径通向a,则称a、b两点 强连通(strongly connected)。
强连通图:如果有向图 G的任意两个顶点都强连通,称G是一个强连通图。
强连通分量:非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。
后向边 :子孙指向祖先的边
Tarjan算法思想:
在深搜的基础下,遍历当前点的所有子孙,找出该点与子孙中所能找到的最早的祖先。
1.(设当前点为pos)time[pos]=Time 能进行深搜表明该点是第一次访问,故time[pos]可确定
2. low[pos]=Time 暂时认为能找到的最早祖先是自己
3. 遍历所有从pos出发所能到达的点 i:
①若点 i 未被访问:对点 i 重复从1.开始的操作以确定low[i]。而后取low[pos]=min( low[pos], low[i] )
②若点 i 在访问中:取low[pos]=min( low[pos], time[i] )
③若点 i 已访问过:什么都不做(它的子孙所在的最大强连通分量已经确定了)
4. 若low[pos]依然是pos,说明pos及其子孙构成了一个强连通分量,计数器应加一
5. 将点pos的状态设置为已访问
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <cmath>
#include <algorithm>
#define IN freopen("in.txt", "r", stdin)
#define OUT freopen("out.txt", "wb", stdout)
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define N 10020
using namespace std;
struct node{
int point;
node *next;
};
int con,n;
int time[N]; //访问该点时的时间(间接指出了先后关系)
int low[N]; //代表该点所能找到的最早祖先(即最大强连通分量)
node mapa[N]; //邻接表的首结点数组
char state[N]; //代表对应点的状态:0代表未被访问过、1代表正在访问、2表示访问完毕
/**
@参数1:在第pos个点执行tarjan操作 参数2:访问pos点时的时间
*/
void dfs_tarjan(int pos,int Time)
{
node *p;
time[pos]=Time; //得到访问时间
state[pos]=1; //标记为正在访问
low[pos]=Time;
for(p=&mapa[pos];p->next;p=p->next)
{
int i=p->next->point;
if(state[i]==0) //未访问
{
dfs_tarjan(i,Time+1);
low[pos]=min(low[pos],low[i]); //在这之前以通过深搜得出了子孙的“low”,取二者最早祖先即可
}else if(state[i]==1)//i点正在访问
low[pos]=min(low[pos],time[i]); //最早祖先当然是 目前结果 和 i的最早祖先 中最早的那个啦(求最大嘛)
}
if(low[pos]==time[pos]) //若pos的所有子孙访问完了,pos所能访问到的最早祖先还是自己,说明自己极其子孙构成了一个最大强连通分量
con++;
state[pos]=2; //该点访问结束
}
int main()
{
IN;
int i,m;
int a,b,num;
node *temp;
while(cin>>n>>m,m||n)
{
for(i=1;i<=n;i++)
mapa[i].next=NULL;
while(m-->0)
{
cin>>a>>b;
temp=(node*)malloc(sizeof(node));
temp->point=b;
temp->next=mapa[a].next;
mapa[a].next=temp;
}
memset(state+1,0,sizeof(char)*(n+1));
con=0;
for(i=1;i<=n;i++) //遍历每一个点
if(state[i]!=2) //若未被访问执行深搜操作
dfs_tarjan(i,0);
if(con==1)
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
return 0;
}