POJ 1236 Network of Schools(强连通分量,缩点)

kuangbin老师讲的非常好。。直接拷他的思路吧。

强连通分量缩点求入度为0的个数和出度为0的分量个数

题目大意:N(2<N<100)各学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输,问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件。2,至少需要添加几条传输线路(),使任意向一个学校发放软件后,经过若干次传送,网络内所有的学校最终都能得到软件。

 

也就是:

—        给定一个有向图,求:

 

1) 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点

 

2) 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点

 

—        顶点数<= 100

解题思路:

—        1. 求出所有强连通分量

—        2. 每个强连通分量缩成一点,则形成一个有向无环图DAG

—        3. DAG上面有多少个入度为0的顶点,问题1的答案就是多少

DAG上要加几条边,才能使得DAG变成强连通的,问题2的答案就是多少

加边的方法:

要为每个入度为0的点添加入边,为每个出度为0的点添加出边

假定有 n 个入度为0的点,m个出度为0的点,如何加边?

把所有入度为0的点编号 0,1,2,3,4 ....N -1

每次为一个编号为i的入度0点可达的出度0点,添加一条出边,连到编号为(i+1)%N 的那个出度0,

这需要加n条边

 m <= n,则

加了这n条边后,已经没有入度0点,则问题解决,一共加了n条边

 m > n,则还有m-n个入度0点,则从这些点以外任取一点,和这些点都连上边,即可,这还需加m-n条边。

所以,max(m,n)就是第二个问题的解

此外:当只有一个强连通分支的时候,就是缩点后只有一个点,虽然入度出度为0的都有一个,但是实际上不需要增加清单的项了,所以答案是10



#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<stack>
using namespace std;
const int maxn = 1e3+5;
int n, low[maxn], dfn[maxn], belong[maxn], dfs_clock, scc_cnt;
int in[maxn], out[maxn];
vector<int> g[maxn];
stack<int> s;

void tarjan(int u)
{
    dfn[u] = low[u] = ++dfs_clock;
    s.push(u);
    for(int i = 0; i < g[u].size(); i++)
    {
        int v = g[u][i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(!belong[v])
            low[u] = min(low[u], dfn[v]);
    }
    if(low[u] == dfn[u])
    {
        scc_cnt++;
        while(1)
        {
            int x = s.top(); s.pop();
            belong[x] = scc_cnt;
            if(x == u) break;
        }
    }
}

void find_scc()
{
    while(!s.empty()) s.pop();
    dfs_clock = scc_cnt = 0;
    memset(belong, 0, sizeof(belong));
    memset(dfn, 0, sizeof(dfn));
    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            tarjan(i);
}

void solve()
{
    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(belong[u] != belong[v])
            {
                in[belong[v]]++;
                out[belong[u]]++;
            }
        }
    }
    int in_cnt = 0, out_cnt = 0;
    for(int i = 1; i <= scc_cnt; i++)
    {
        if(!in[i]) in_cnt++;
        if(!out[i]) out_cnt++;
    }
    printf("%d\n%d\n", in_cnt, max(in_cnt, out_cnt));
}

int main(void)
{
    while(cin >> n)
    {
        for(int i = 0; i < maxn; i++)
            g[i].clear();
        for(int i = 1; i <= n; i++)
        {
            int t;
            while(cin >> t, t)
                g[i].push_back(t);
        }
        find_scc();
        if(scc_cnt == 1) printf("1\n0\n");  //注意特判只有一个连通分量
        else solve();
    }
    return 0;
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值