Network of Schools (POJ 1236)

Description

A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the “receiving schools”). Note that if B is in the distribution list of school A, then A does not necessarily appear in the list of school B 
You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school. 

Input

The first line contains an integer N: the number of schools in the network (2 <= N <= 100). The schools are identified by the first N positive integers. Each of the next N lines describes a list of receivers. The line i+1 contains the identifiers of the receivers of school i. Each list ends with a 0. An empty list contains a 0 alone in the line.

Output

Your program should write two lines to the standard output. The first line should contain one positive integer: the solution of subtask A. The second line should contain the solution of subtask B.

Sample Input

5
2 4 3 0
4 5 0
0
0
1 0

Sample Output

1
2
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

//BY 周洋师弟;

/*
解题思路:
求出所有强连通分量,每个分量视作一个点;
求入度为0和出度为0的“点”;
对于问题2,要为每个入度为0的点添加入边,为出度为0的点添加出边;
假设有n个入度为0的点,m个出度为0的点。max(m, n)是第二问的答案;
*/

const int MAX_V = 110;
int V;
vector G[MAX_V]; //图;
vector rG[MAX_V]; //逆图;
vector vs; //反向深搜对应的顶点;
bool used[MAX_V]; //记录点是否被经过;
int used_num[MAX_V]; //记录点被经过一次还是两次;
vector block[MAX_V]; //每个强连通分量所含点的集合;
void add_edge(int from, int to)
{
    G[from].push_back(to);
    rG[to].push_back(from);
}
void dfs(int v)
{ //正向深搜;
    used[v] = true;
    for (int i = 0; i < G[v].size(); i++)
    {
        if (!used[G[v][i]])
            dfs(G[v][i]);
    }
    vs.push_back(v); //结束时间晚的被最后push进vs中;
}
void rdfs(int v, int k)
{ //逆向深搜时,需要给强连通分量标记序号,故加入k一维;
    used[v] = true;
    block[k].push_back(v); //形成由不同点集所代表的不同的强连通分量,存储于block;
    for (int i = 0; i < rG[v].size(); i++)
    {
        if (!used[rG[v][i]])
            rdfs(rG[v][i], k);
    }
}
void check_dfs(int v)
{ //辅助得出出度入度为0的连通分量数;
    used[v] = true;
    used_num[v]++;
    for (int i = 0; i < G[v].size(); i++)
    {
        if (!used[G[v][i]])
            check_dfs(G[v][i]);
    }
}
int scc()
{ //强连通分量个数;
    memset(used, 0, sizeof(used));
    vs.clear();
    for (int v = 0; v < V; v++)
    { //形成按结束时间先后排序的vs向量,存储在向量最后面的是结束时间最晚的顶点;
        if (!used[v])
            dfs(v);
    }
    memset(used, 0, sizeof(used));
    int k = 0;
    for (int i = vs.size() - 1; i >= 0; i--)
    { //从结束时间最晚的顶点开始逆向深搜,得出强连通分量的个数;
        if (!used[vs[i]])
            rdfs(vs[i], k++);
    }
    return k;
}

int main(){
    int a;
    cin >> V;
    for (int i = 0; i < V; i++)
    { //输入“认为出名”关系;
        cin >> a;
        while (a != 0)
        {
            add_edge(i, a - 1);
            cin >> a;
        }
    }
    // 统计入度为零的点的个数m,以及出度为零的点的个数n;
    int m = 0, n = 0; //m记录入度为0的连通分量数,n记录出度为0的连通分量数;
    int num = scc(); //强连通分量个数;
    if (num == 1)
        cout << 1 << endl << 0 << endl;
    else
    {
        memset(used_num, 0, sizeof(used_num));
        for (int i = 0; i < num; i++)
        { //i是出度为0的连通分量吗;
            memset(used, 0, sizeof(used));//考虑分量i'时,i走过的痕迹被抹去;
            //因而used_num可以记录被经过次数,而used只能记录当前循环中某店是否被经过;
            check_dfs(block[i][0]); //对强连通分量i进行深搜;
            //考察是否会到达其他的强连通分量;
            int flag = 0;
            for (int j = 0; j < num; j++)
            { //是否对其他分量进行了“染色”;
                if (j != i)
                {
                    if (used[block[j][0]] == true)
                    {
                        flag = 1;
                        break;
                    }
                }
            }
            if (flag == 0) //该连通分量出度为0,也可理解为通过深搜无法到达其他连通分量;
                n++;
        }
        for (int i = 0; i < num; i++)
        { //i是入度为0的连通分量吗;
            if (used_num[block[i][0]] == 1)
                m++;
        }
        cout << m << endl;
        cout << max(m, n) << endl; //证明难,略;
    }
    return 0;
}



你可能感兴趣的:(强连通分量)