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

题意

有向图上有 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];                    //更新根
        }
        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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值