强连通的点(即两点之间有路径可以相互到达),这道迷宫城堡题便是计算有几个强连通分量。
先说Tarjan,他的主体部分便是:
1 void tarjan(int a) 2 { 3 checklow[a]=low[a]=++timer; 4 visit[a]=1; 5 for(int i=0;i<vec[a].size();i++) 6 { 7 int x=vec[a][i]; 8 if(visit[x]==0) tarjan(x); 9 if(visit[x]==1) low[a]=min(low[a],low[x]); 10 } 11 }
原理如下:checklow用于储存low未改变之前的值,low值在递归时可能会被更新,timer代表点a的权值,x代表下一个点,visit[i]表示有没有被访问;
举个例子:有(1,2),(2,3),(3,4),(4,1)四组数据,tarjan(1)时,点1的权值为1,点1被标记访问,因为2未访问,故tarjan(2),点2权值为2且被标记访问,一直递归至点4后(此时:checklow[1]=low[1]=1,checklow[2]=low[2]=2,checklow[3]=low[3]=3,checklow[4]=low[4]=4);往1走,因为1被访问(即找到了可以形成圈的点了),故low[4]被更新为min(low[1],low[4])=1;然后返回到low[3]=min(low[3],low[4])=1;一直更新到low[1]=low[2]=1;所以点1,2,3,4全部成了满足条件权值为1的点了(4个点中现在只有1的checklow=low了,而且一个强连通分量有且只有一个点1);如果最后(4,1)改成(4,2),那么点2,3,4的权值为2,点1权值不变,因为退到点1递归时,low[1]=min(low[1],low[2])还是等于1,点2只会与点3(点3为点2的下一个点)作权值比较;Tarjan算法原理就是这个了,网上有很多大牛画图解释,讲的蛮好的;以下就是HDU 1269的解题代码:
1 #include<iostream> 2 #include<cstring> 3 #include<vector> 4 using namespace std; 5 vector<int> vec[10005];//容器i代表第几个房间,值代表通向哪个房间; 6 int checklow[10005],low[10005],visit[10005],timer,count; 7 int min(int a,int b) 8 { 9 return a>b?b:a; 10 } 11 void tarjan(int a) 12 { 13 checklow[a]=low[a]=++timer; 14 visit[a]=1; 15 for(int i=0;i<vec[a].size();i++) 16 { 17 int x=vec[a][i]; 18 if(visit[x]==0) tarjan(x); 19 if(visit[x]==1) low[a]=min(low[a],low[x]);//当前房间与下一个房间比较; 20 } 21 } 22 int main() 23 { 24 int n,m; 25 while(cin>>n>>m&&n) 26 { 27 int a,b; 28 while(m--) 29 { 30 cin>>a>>b; 31 vec[a].push_back(b);//第几号房间存入连通房间 32 } 33 memset(visit,0,sizeof(visit)); 34 memset(checklow,0,sizeof(checklow)); 35 memset(low,0,sizeof(low)); 36 count=timer=0; 37 for(int i=1;i<=n;i++)//这样能把所有的房间遍历一遍; 38 if(!visit[i]&&!vec[i].empty()) 39 tarjan(i); 40 for(int i=1;i<=n;i++) 41 if(checklow[i]==low[i]) 42 count++;//计算有多少组强连通分量 43 if(count==1)//说明只有一组强连通分量,满足题意; 44 cout<<"Yes"<<endl; 45 else 46 cout<<"No"<<endl; 47 for(int i=1;i<=n;i++) 48 vec[i].clear();//清空容器,不然上一组容器中存的数据会影响后面的判断; 49 } 50 return 0; 51 }
这个算法曾经花了好久才学明白,都是一些细节没弄通,但是一步一步打草稿演算就慢慢弄懂了,收获很多。