tarjan缩点构图

2218: Thrall’s Dream

Time Limit: 1 Sec  Memory Limit: 128 MB

Description

We never paid any heed to the ancient prophecies, like fools we clung to the old hatreds, and fought as we had for generations. Until one day the sky rained fire, and a new enemy came upon us. We stand now upon the brink of destruction, for the Reign of Chaos has come at last.

Thrall, the warchief of the Orcish Horde, all along, he led his tribe live in the fringe of Lordaeron under the human control. In a downpour night, Thrall falls into sleep in a Orc hall at Arathi Highlands, at this moment he heard a voice:

“The sands of time have run out, son of Durotan. The cries of war echo upon the winds, the remnants of the past scar the land which is besieged once again by conflict. Heroes arise to challenge fate, and lead their brethren to battle. As mortal armies rush blindly towards their doom, The Burning Shadow comes to consume us all. You must rally the Horde, and lead your people to their destiny.

I will answer all of your questions in time, young warchief. For now, rally your warriors and prepare to leave this land, cross the sea to the distant land of Kalimdor. We will speak again. ”

 

Thrall believes the prophesy of Blood Raven Medivh. Three days later, He and Grom Hellscream's Warsong Clan meet in the Lordaeron coast to the distant lands of Kalimdor. But the Goblin Zeppelins they take encountered storms in the middle. Thrall and Grom falling to the islands, they want to find each other and then to Kalimdor.

For the sake of simplicity, we assume that Thrall and Grom may fall into any islands x and y, only by Thrall to find Grom or by Grom to find Thrall. Give you the map of this island, please judge that Thrall and Gtom can meet?

 

Input

There are multiple test case in the input file, first line is a case number T. Each test case will begin with two integers N (0 <= N < 2001) and M (0 <= M < 10001), where N is the number of islands and M is number of portal. Next M lines each line contains two integers a and b, indicated there is a portal in island a that people can go from a to b by this portal. The island numbered from 1 to N.

 

Output

For each test case, your output should be in one line with “Kalimdor is just ahead” (without quotes, hereinafter the same) if Thrall and Grom can meet or “The Burning Shadow consume us all” otherwise as indicated in the sample output. 

 

Sample Input

2 3 2 1 2 1 3 3 2 1 2 2 3

Sample Output

Case 1: The Burning Shadow consume us all Case 2: Kalimdor is just ahead

HINT

 

Source

2013年山东省第四届ACM大学生程序设计竞赛

 思路:  判断缩点后的图是否是一条链即可 ;
#include <iostream>
#include <stdio.h>
#include <string>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <fstream>
#include <vector>
using namespace std ;
const int size=10008 ;
struct EDGE{
    int v ;
    int next ;
}edge[size] ;
int id ;
int vec[size] ,mystack[size] ,top;
int low[size] ,dfn[size] ,idx ,num ;
bool instack[size] ;
int belong[size] ;
inline void add_edge(int u,int v){
    edge[id].v=v ;
    edge[id].next=vec[u] ;
    vec[u]=id++ ;
}
void tarjan(int u){
   low[u]=dfn[u]=idx++ ;
   mystack[++top]=u ;
   instack[u]=1 ;
   for(int e=vec[u];e!=-1;e=edge[e].next){
       int v=edge[e].v ;
       if(dfn[v]==-1){
          tarjan(v) ;
          low[u]=min(low[u],low[v]) ;
       }
       else if(instack[v])
          low[u]=min(low[u],dfn[v]) ;
   }
   if(low[u]==dfn[u]){
       int v ;
       num++ ;
       do{
          v=mystack[top--] ;
          instack[v]=0 ;
          belong[v]=num ;
       }while(v!=u) ;
   }
}
void init(){
    idx=1 ;
    top=-1 ;
    num=0 ;
    id=0;
    memset(dfn,-1,sizeof(dfn)) ;
    memset(vec,-1,sizeof(vec)) ;
    memset(instack,0,sizeof(instack)) ;
}
bool visited[2008][2008] ;
bool grid[2008][2008] ;
int in_degree[2008] ,out_degree[2008] ;
int main(){
  int i ,j ,n, m,u,v,cas=1;
  int T ;
  cin>>T ;
  while(T--){
      scanf("%d%d",&n,&m) ;
      init() ;
      memset(visited,0,sizeof(visited)) ;
      while(m--){
          scanf("%d%d",&u,&v) ;
          if(u==v)
             continue ;
          if(!visited[u][v]){
              add_edge(u,v) ;
              visited[u][v]=1 ;
          }
      }
      for(i=1;i<=n;i++){
         if(dfn[i]==-1)
            tarjan(i) ;
      }
      if(num==1){
          printf("Case %d: Kalimdor is just ahead\n",cas++) ;
          continue ;
      }
      memset(grid,0,sizeof(grid)) ;
      for(u=1;u<=n;u++){
        for(int e=vec[u];e!=-1;e=edge[e].next){
           v=edge[e].v ;
           if(belong[u]!=belong[v]){
              grid[belong[u]][belong[v]]=1 ;
           }
        }
      }
      memset(in_degree,0,sizeof(in_degree)) ;
      memset(out_degree,0,sizeof(out_degree)) ;
      for(int i=1;i<=num;i++)
         for(int j=1;j<=num;j++){
             if(grid[i][j]){
                   out_degree[i]++ ;
                   in_degree[j]++ ;
             }
      }
      int two=0 ;
      int in_one=0 ;
      int out_one=0 ;
      for(int i=1;i<=num;i++){
          if(in_degree[i]==1&&out_degree[i]==1)
               two++ ;
          else if(in_degree[i]==0&&out_degree[i]==1)
               out_one++ ;
          else if(in_degree[i]==1&&out_degree[i]==0)
               in_one++ ;
      }
      if(two==num-2&&in_one==1&&out_one==1)
          printf("Case %d: Kalimdor is just ahead\n",cas++) ;
      else
          printf("Case %d: The Burning Shadow consume us all\n",cas++) ;

  }
   return 0;
}

  

[有向图强连通分量]

在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(stronglyconnected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。

下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。

tarjan缩点构图_第1张图片

直接根据定义,用双向遍历取交集的方法求强连通分量,时间复杂度为O(N^2+M)。更好的方法是Kosaraju算法或Tarjan算法,两者的时间复杂度都是O(N+M)。本文介绍的是Tarjan算法。

[Tarjan算法]

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。由定义可以得出,

Low(u)=Min{DFN(u),Low(v),(u,v)为树枝边,u为v的父节点
           DFN(v),(u,v)为指向栈中节点的后向边(非横叉边)}

当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。

算法伪代码如下

tarjan(u)
{
 DFN[u]=Low[u]=++Index        // 为节点u设定次序编号和Low初值
 Stack.push(u)                // 将节点u压入栈中
 for each (u, v) in E         // 枚举每一条边
  if (v is not visted)        // 如果节点v未被访问过
   tarjan(v)                  // 继续向下找
   Low[u] = min(Low[u], Low[v])
  else if (v in S)            // 如果节点u还在栈内
   Low[u] = min(Low[u], DFN[v])
 if (DFN[u] == Low[u])        // 如果节点u是强连通分量的根
  repeat
   v = S.pop                 // 将v退栈,为该强连通分量中一个顶点
   print v
  until (u== v)
}

接下来是对算法流程的演示。

从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。

tarjan缩点构图_第2张图片

返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。

tarjan缩点构图_第3张图片

返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4像节点1的后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,不再访问6,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。

tarjan缩点构图_第4张图片

继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=4。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。

tarjan缩点构图_第5张图片

至此,算法结束。经过该算法,求出了图中全部的三个强连通分量{1,3,4,2},{5},{6}。

可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。

求有向图的强连通分量还有一个强有力的算法,为Kosaraju算法。Kosaraju是基于对有向图及其逆图两次DFS的方法,其时间复杂度也是 O(N+M)。与Trajan算法相比,Kosaraju算法可能会稍微更直观一些。但是Tarjan只用对原图进行一次DFS,不用建立逆图,更简洁。 在实际的测试中,Tarjan算法的运行效率也比Kosaraju算法高30%左右。此外,该Tarjan算法与求无向图的双连通分量(割点、桥)的Tarjan算法也有着很深的联系。学习该Tarjan算法,也有助于深入理解求双连通分量的Tarjan算法,两者可以类比、组合理解。

求有向图的强连通分量的Tarjan算法是以其发明者Robert Tarjan命名的。Robert Tarjan还发明了求双连通分量的Tarjan算法,以及求最近公共祖先的离线Tarjan算法,在此对Tarjan表示崇高的敬意。

void tarjan(int i)
{
 int j;
 DFN[i]=LOW[i]=++Dindex;
 instack[i]=true;
 Stap[++Stop]=i;
 for (edge *e=V[i];e;e=e->next)
 {
  j=e->t;
  if (!DFN[j])
  {
   tarjan(j);
   if (LOW[j]<LOW[i])
    LOW[i]=LOW[j];
  }
  else if (instack[j] && DFN[j]<LOW[i])
   LOW[i]=DFN[j];
 }
 if (DFN[i]==LOW[i])
 {
  Bcnt++;
  do
  {
   j=Stap[Stop--];
   instack[j]=false;
   Belong[j]=Bcnt;
  }
  while (j!=i);
 }
}
void solve()
{
 int i;
 Stop=Bcnt=Dindex=0;
 memset(DFN,0,sizeof(DFN));
 for (i=1;i<=N;i++)
  if (!DFN[i])
   tarjan(i);
}

 

你可能感兴趣的:(tarjan缩点构图)