POJ 1236 Network Of Schools ( tarjan求强连通分量 + 缩点成DAG图 )

/**
*      tarjan + 缩点:
*      这题题意其实就是求:强连通分量数(task1) 和 在对强连通分量缩点后形成的
*   DAG图加至少多少条边,能使其成为一个强连通图(也就是题目所说的任何一个点出
*   去都能到图中任何一个点。)
*      怎么算强连通分量,怎么缩点。 其实tarjan算法就行,理解tarjan后,把所有点
*   都可以用数组belong去分配到某一个强连通分量中去。  缩点就是把每个强连通分量
*   看成一个节点和剩下的不能再构成强连通分量的有向边就构成了一个有向无环图(DAG)
*   然后对各点计算他的出度入度。
*      在完成tarjan和缩点之后。。就是怎么理解题意了。
*     任务一:不改变原图,至少要发多少个软件。。在一个强连通分量里发一个就能连到
*   这个强连通分量里的任一节点。 这里就体现了为什么缩点,每个强连通分量只要有个软件传来
*   就可以遍历所有节点了。 所以只要考虑之后的DAG图就行了。如果在每个入度为0的地方放
*   个软件,是不是就可以到达能够连通的所有缩点?  这时候其实就是计算有多少个入度为0的缩点
*
*     任务二: 如何加边使得,从任何一个缩点出发都能到达其余任何点。 这其实就是之前说的
*   加边让原来的DAG图变成一个强连通图。
*      关键是怎么加边。。首先一个强连通图肯定是不存在一个点只有入度或者只有出度的。
*   如果能够让每个点的入度=出度(把一个出度为零的点加条边指向入度为零的那个点,不就是最大的
*   发挥了这条边的作用了吗),再通过把原来的分离的DAG图相连接就行了。
*      换句话说,我只要找出出度为零 和 入度为零中的较大者就是task2的答案了。这个可以自己画画图。
*/

#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <algorithm>
#define DEBUG 0
#define INF 0x7fffffff
#define MAXS 101

typedef long long LL;
using namespace std;
int gra[MAXS][MAXS], dfn[MAXS], low[MAXS], st[MAXS], vis[MAXS], belong[MAXS];
int in[MAXS], out[MAXS];
int n, rear, numOfSCC, dfs_colock;

void init() {
    rear = 0;
    numOfSCC = dfs_colock = 1;
    memset(gra, 0, sizeof(gra));
    for(int i = 0; i <= n; i ++ ) {
        in[i] = out[i] = vis[i] = low[i] = 0;
        dfn[i] = -1;   belong[i] = 0;
    }
}

void tarjan(int x) {
    dfn[x] = low[x] = dfs_colock ++;
    st[rear ++] = x;
    vis[x] = 1;
    for(int i = 1; i <= n; i ++) {
        if(!gra[x][i]) continue;
        if(dfn[i] == -1) {
            tarjan(i);
            low[x] = min(low[x], low[i]);
        } else if(vis[i]) {
            low[x] = min(low[x], dfn[i]);
        }
    }
    /** 在进行完对其子孙节点的tarjan后,也就是更新最小值后,low[x]仍然没变,说明x就是个强连通分量的根节点。*/
    if(low[x] == dfn[x]) {
        int t;
        while(true) {
            t = st[-- rear];
            belong[t] = numOfSCC;
            vis[t] = 0;
            if(t == x) break;
        }
        numOfSCC ++;
    }
}



void solve() {
    for(int i = 1; i <= n; i ++) {
        if(dfn[i] == -1)
            tarjan(i);
    }
    /** 缩点。*/
    for(int i = 1; i <= n; i ++) {
        for(int j = 1; j <= n; j ++) {
            if(gra[i][j] && belong[i] != belong[j]) {
                out[belong[i]] ++;
                in[belong[j]] ++;
            }
        }
    }
    /**  计算缩点后的DAG图中各节点的出入度。 */
    int numIn = 0, numOut = 0;
    for(int i = 1; i < numOfSCC; i ++) {
        if(!in[i])  numIn ++;
        if(!out[i]) numOut ++;
    }
    if(numOfSCC == 2) printf("1\n0\n");
    else {
        printf("%d\n", numIn);
        printf("%d\n", max(numIn, numOut));
    }
}

int main()
{
    while(scanf("%d", &n) != EOF) {
        init();
        for(int i = 1; i <= n; i ++) {
            for(int j = 1; j <= n; j ++) {
                int cur;  scanf("%d", &cur);
                if(!cur) break;
                gra[i][cur] = 1;
            }
        }

        solve();
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值