POJ2186 图论 Tarjan求SCC

这篇博客介绍了如何利用Tarjan算法解决有向图中强连通分量(SCC)的问题。首先,通过DFS判断底图是否连通,然后应用Tarjan算法找到所有SCC并进行缩点操作。最后,再次进行DFS检查缩点后的新图中各缩点的出度,以此判断是否可以从任何顶点到达这些点。在实现过程中要注意防止额外的输出干扰,以及如何优化数据结构以存储更多信息。
摘要由CSDN通过智能技术生成

题意:
给定一个有向图,求有多少个顶点是由任何一个顶点出发都可达的。
思路:
定理:DAG中唯一出度为0的点,一定可以由任何点出发均可达(由于无环,所以从任何点出发往前走,必然终止于一个出度为0的点)。
另外,答案为0的情况有两种:一为底图不连通,二为有不止一个出度有0的缩点。

大致流程:
1、DFS一遍查底图是否连通;
2、Tarjan算法求所有SCC并缩点;
3、再DFS一遍新图(缩点之后的图)求每个缩点的出度。
注意:
缩点的时候不一定要构造新图,只要把不同强连通分量的点染不同颜色,然后考察各种颜色的点有没有连到别的颜色的边即可(即其对应的缩点后的DAG图上的点是否有出边)。

反思:
1、提交前一定要查看是否把一些自己加的输出给注释掉了!
2、诸如inStack数组可以改为int型的数组来记录更多的信息,
如 0:未进过栈 1:在栈中 2:曾在栈中
代码:

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

using namespace std;

const int MAXN = 10000 + 10;

vector<int> graph[MAXN];
vector<int> graph1[MAXN];//有向图的底图,用于检验是否连通
int inStack[MAXN];//0:未进过栈 1:在栈中 2:曾在栈中
int st[MAXN], low[MAXN], dfn[MAXN];
int top, cnt, num;//cnt:顶点数量 num:scc数量
int amt[MAXN];//用于记录每个scc中的顶点个数
int belong[MAXN];//每个顶点从属于哪个lcc
int n, m;

int cnt1;//底图一次遍历到的顶点个数
bool vis[MAXN];

bool vis1[MAXN];
int outdegree[MAXN];//统计每个缩点的出度

void tarjan(int u)
{
    dfn[u] = low[u] = ++cnt;
    st[top++] = u;
    inStack[u] = 1;
    for(int i = 0; i < graph[u].size(); i++)
    {
        int v = graph[u][i];
        if(inStack[v] == 1)
        {
            low[u] = min(low[u], dfn[v]);
        }
        if(!inStack[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
    }
    if(dfn[u] == low[u])
    {
        ++num;
        int x;
        do
        {
            x = st[--top];
            inStack[x] = 2;
            belong[x] = num;
            amt[num]++;
        }
        while(x != u);
    }
    return;
}

void init()
{
    for(int i = 0; i <= n; i++)
    {
        graph[i].clear();
        graph1[i].clear();
    }
    memset(inStack, 0, sizeof(inStack));
    memset(amt, 0, sizeof(amt));
    memset(belong, 0, sizeof(belong));
    memset(vis, false, sizeof(vis));
    memset(vis1, false, sizeof(vis1));
    memset(outdegree, 0, sizeof(outdegree));
    top = cnt = num = 0;
    cnt1 = 0;
}

void dfs(int u)
{
    cnt1++;
    vis[u] = true;
    for(int i = 0; i < graph1[u].size(); i++)
    {
        int v = graph1[u][i];
        if(!vis[v]) dfs(v);
    }
}

void dfs1(int u)//统计每个缩点的出度
{
    vis1[u] = true;
    for(int i = 0; i < graph[u].size(); i++)
    {
        int v = graph[u][i];
        if(belong[u] != belong[v])
        {
            outdegree[belong[u]]++;
        }
        if(!vis1[v])
        {
            dfs1(v);
        }
    }
}
int main()
{
    while(~scanf("%d%d", &n, &m))
    {
        init();

        for(int i = 0; i < m; i++)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            graph[u].push_back(v);
            graph1[u].push_back(v);
            graph1[v].push_back(u);
        }

        dfs(1);
        if(cnt1 != n)
        {
            printf("0\n");
            continue;
        }

        for(int i = 1; i <= n; i++)
        {
            if(!inStack[i])
            {
                tarjan(i);
            }
        }
        for(int i = 1; i <= n; i++)
        {
            if(!vis1[i])
            {
                dfs1(i);
            }
        }
        int sum = 0, pos;
        for(int i = 1; i <= num; i++)
        {
            if(outdegree[i] == 0) sum++, pos = i;
        }
        if(sum == 1)
        {
            printf("%d\n", amt[pos]);
        }
        else printf("0\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值