您的位置:首页 > 其它

POJ1236_A - Network of Schools _强连通分量::Tarjan算法

2017-07-15 14:35 429 查看

题意

有向图上有 N 个点,若干有向边。

第一问:至少给几个点传递信息,才能保证信息传遍整个图。

第二问:至少添加几条边,才能使任意选择点,都能传遍整个图。

思路

强连通分量的裸题。

强连通分量内的任意一点收到消息,内部其他各点必定都能收到消息。因此,可以把每个强连通分量缩成一个点。只需要考察入度为 0 的强连通分量的个数,就是第一问的答案。

对于第二问,是把图连接成一个强连通分量,同样可以在缩点后的图中操作。这里的做法是统计图中入度为0、出度为0的强连通分量的个数,取较大值即为第二问的答案。

原图中各ssc之间不会成环,如果成环的话就是一个ssc了。要使整个图强连通,至少要使所有分量的入度和出度都为0。这样也是必定存在合法方案的。

本题中原图只有一个强连通分量的情况需要特判。

Tarjan算法

Tarjan算法是基于bfs查找强连通分量的方法。

每个点赋予三个附加属性。时间戳,发现该点的时间;根,该节点所在强连通分量中的最小时间戳;是否在栈中标记。

从任意一点出发,对图进行bfs。

每进入一个点,标记时间戳,将根置为本身,压栈。然后搜索与它相连的其他各点。如果没有到达过,则进入那个点。如果已经到达过,并且那个点现在仍在栈中,说明这两个点在同一个强连通分量里。比较当前点的根和那个点的时间戳,如果那个点的时间戳较小,则把根改成那个点的时间戳。即这两个点并入了同一个强连通分量。

沿bfs路径返回的时候,首先更新根节点(因为出现环表示发现了强连通分量,而从下网上返回的时候靠下的点的根是真的根),并检查每个点的时间戳和根是否相等。如果相等,说明当前点就是它所在强连通分量的根。而此时的栈中,比它后进栈的元素都在这个强连通分量中。更新ssc个数,将这些点连通当前点加入一个ssc中,并全部弹栈。

题目链接

http://poj.org/problem?id=1236

AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>

using namespace std;

const int maxn = 100 + 10;

int N;
int In[maxn], Out[maxn];

/***************************Tarjan算法模板***************************/
vector<int> G[maxn];
int Mark[maxn], Root[maxn], Stack[maxn];                                //时间戳,根(当前分量中时间戳最小的节点),栈
bool Instack[maxn];                                                     //是否在栈中标记
int Ssc[maxn];                                                          //每个节点所在的强连通分量的编号
int Index, Ssc_n, Top;                                                  //搜索时用的时间戳,强连通分量总数,栈顶指针

void Tarjan(int u)                                                      //u 当前搜索到的点
{
Mark[u] = Root[u] = ++ Index;                                       //每找到一个点,对时间戳和根初始化
Stack[Top ++] = u;                                                  //压栈
Instack[u] = true;                                                  //在栈中标记

int v;

for(int i= 0; i< G[u].size(); i++)                                  //向下搜索
{
v = G[u][i];
if(Mark[v] == 0)                                                //没到过的点
{
Tarjan(v);                                                  //先向下搜索
if(Root[u] > Root[v]) Root[u] = Root[v];
c8cd
//更新根
}
else if(Instack[v] && Root[u] > Mark[v]) Root[u] = Mark[v];     //到过的点且点仍在栈中,试着看这个点能不能成为根
}
/*对当前点的搜索结束*/
if(Mark[u] == Root[u])                                              //当前点本身时根
{
Ssc_n ++;                                                       //更新强连通分量数

do{                                                             //栈中比它后入栈的元素在以它为根的强连通分量中
v = Stack[-- Top];
Instack[v] = false;
Ssc[v] = Ssc_n;
}while(v != u);                                                 //直到它自己
}
}

void SSC()
{
memset(Mark, 0, sizeof Mark);                                       //初始化时间戳和栈内标记
memset(Instack, false, sizeof Instack);
Index = Ssc_n = Top = 0;                                            //初始化时间戳,强连通分量数,栈顶指针

for(int i= 1; i<= N; i++)                                           //保证图上所有点都访问到
if(Mark[i] == 0) Tarjan(i);
}
/***************************Tarjan算法模板***************************/

int main()
{
//freopen("in.txt", "r", stdin);

scanf("%d", &N);
for(int i= 1; i<= N; i++)
{
int x;
while(scanf("%d", &x), x)
G[i].push_back(x);
}

SSC();

if(Ssc_n == 1)                                                      //只有一个强连通分量的情况
{
cout << "1\n0\n";
return 0;
}

memset(In, 0, sizeof In);                                           //求每个强连通分量的入度和出度
memset(Out, 0, sizeof Out);
for(int u= 1; u<= N; u++)
{
for(int i= 0; i< G[u].size(); i++)
{
int v = G[u][i];
if(Ssc[u] != Ssc[v])
Out[Ssc[u]] ++, In[Ssc[v]] ++;
}
}

int S1 = 0, S2 = 0;                                                 //找入度为0、出度为0的点的数目
for(int i= 1; i<= Ssc_n; i++)
{
if(In[i] == 0) S1 ++;
if(Out[i] == 0) S2 ++;
}

cout << S1 << endl << max(S1, S2) << endl;

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: