poj_1236 强连通分支

题目大意

    有N个学校,这些学校之间用一些单向边连接,若学校A连接到学校B(B不一定连接到A),那么给学校A发一套软件,则学校B也可以获得。现给出学校之间的连接关系,求出至少给几个学校分发软件,才能使得所有的学校均可以获得软件;以及,至少需要添加几条单向边连接学校,才能使得给这些学校中任何一所发软件,其余的学校均可以收到。

题目分析

    在一个图中,强连通分支内的任何一个点被“发软件”,则分支内的所有点均可以获得,因此首先求出强连通分支,将强连通分支合并为一点来看。 
    重构之后的图若只有一个点,则只需要向任何一所学校发送即可。即结果为1(至少向1所学校发布软件) 0(不需要添加新边来使得整个图连通). 
    重构之后的图若有多个点,则考虑这些点中入度为0的点:入度为0的点不能被其他点到达,而一个入度不为0的点可以从某个入度为0的点到达,那么只需要向这些入度为0的点分发软件,就可以使得所有的点均能获得软件。 
    重构之后的图中有出度为0的点,在图中,入度为0的点(设为m个)无法从其他点到达,那么为了使得所有的点连通,需要m条路径连接到这m个入度为0的点;而出度为0的点(设为n个)无法到达其他点,那么为了使得所有的点连通,需要n条路径从这n个出度为0的点连出。于是,至少需要添加 max(m, n)条边,使得图中所有的点的入度和出度不为0. 
    同时,在一个有向无环图中,如果该图的所有点均可连接到一块,且每个点的出度和入度均不为0,则该图肯定强连通。于是,结果为 max(m,n)

实现(c++)

#include<stdio.h>
#include<string.h>
#include<vector>
#include<stack>
#include<set>
#define MAX_NODE 105
#define min(a, b) a < b? a:b
#define max(a, b) a > b? a:b
using namespace std;
vector<vector<int> > gGraph;	//存储图结构
stack<int> gStack;				//用于Tarjan算法

bool gVisited[MAX_NODE];		//判断每个点是否被访问过
bool gInStack[MAX_NODE];		//在Tarjan算法中判断点是否在栈中
int gDfn[MAX_NODE];				//Tarjan算法中标记每个点最开始被访问的时间
int gLow[MAX_NODE];				//Tarjan算法中标记从每个点开始的DFS过程中经过的点所能访问到的点的最小的开始时间

int gClusterOfNode[MAX_NODE];	//标记每个点所在的强连通分支
int gIndex;						//标记点被第一次访问的时间
int gClusterIndex;				//强连通分支的编号

//Tarjan算法求强连通分支
void Tarjan(int u){
	gLow[u] = gDfn[u] = ++gIndex;
	gVisited[u] = true;
	gInStack[u] = true;
	gStack.push(u);

	for (int i = 0; i < gGraph[u].size(); i++){
		int v = gGraph[u][i];
		if (!gVisited[v]){
			Tarjan(v);
			gLow[u] = min(gLow[u], gLow[v]);
		}
		else if (gInStack[v]){
			gLow[u] = min(gLow[u], gDfn[v]);
		}
	}
	if (gLow[u] == gDfn[u]){
		int v;
		do{
			v = gStack.top();
			gStack.pop();
			gInStack[v] = false;
			gClusterOfNode[v] = gClusterIndex;	//标记所属的强连通分支编号
		} while (v != u);
		gClusterIndex++;
	}
}
vector<set<int> > gLinkTo;		//将强连通分支缩成点之后,每个点所连接到的另外的点,用set 使得插入时不重复。用于统计出度
vector<set<int> > gLinkFrom;	//将强连通分支缩成点之后,每个点被其他的点连接,用set 使得插入时不重复。用于统计入度

//将强连通分支缩成点,然后统计每个强连通分支的入度和出度
void ReconstructGraph(int nodes, int clusters){
	gLinkTo.resize(clusters);
	gLinkFrom.resize(clusters);

	for (int u = 1; u <= nodes; u++){
		for (int i = 0; i < gGraph[u].size(); i++){
			int v = gGraph[u][i];
			int uc = gClusterOfNode[u];
			int vc = gClusterOfNode[v];
			if (uc != vc){	//注意!!!自身不能连接到自身!
				gLinkTo[uc].insert(vc);
				gLinkFrom[vc].insert(uc);
			}
		}
	}
}

int main(){
	int n, u, v;
	scanf("%d", &n);
	gGraph.resize(n + 1);
	for (int i = 1; i <= n; i++){
		while (scanf("%d", &v) && v != 0){
			gGraph[i].push_back(v);
		}
	}
	gIndex = gClusterIndex = 0;
	memset(gVisited, false, sizeof(gVisited));
	memset(gInStack, false, sizeof(gInStack));
	for (int u = 1; u <= n; u++){
		if (!gVisited[u]){
			Tarjan(u);
		}
	}
	ReconstructGraph(n, gClusterIndex);
	int zero_outdegree = 0, zero_indegree = 0;
	for (int i = 0; i < gClusterIndex; i++){
		if (gLinkFrom[i].empty()){
			zero_indegree++;
		}
		if (gLinkTo[i].empty()){
			zero_outdegree++;
		}
	}
	if (gClusterIndex == 1){	//如果只有一个强连通分支,则直接输出1 0
		printf("1
0
");
	}else
		//最少需要发送的版本个数,等于将强连通分支缩成点之后的图中 入度为0的点的个数,
		//因为,一个有向无环图中,所有入度不为0的点,一定可以从某个入度为0的点出发可达。
		//而这些入度为0的点之间不能可达,故至少需要将所有这些入度为0的点设为起点

		//需要连接多少条有向边才能使得整个图称为一个强连通图
		//入度为0的点设为m个,不能被其他任何点可达,因此,要有 m条边连接到 这m个入度为0的点
		//出度为0的点设为n个,无法到达其他点,因此要有n条边从这n个出度为0的点连接出去
		//可以证明,在一个有向无环图中,所有点的入度和出度均不为0,则该图为强连通的
		//则,至少有 max(m, n)条边。

		printf("%d
%d
", zero_indegree, max(zero_outdegree, zero_indegree));
	return 0;
}
原文地址:https://www.cnblogs.com/gtarcoder/p/4871267.html