poj_2186 强连通分支

题目大意

    有N头牛,他们中间有些牛会认为另外一些牛“厉害”,且这种认为会传递,即若牛A认为牛B“厉害”,牛B认为牛C“厉害”,那么牛A也认为牛C“厉害”。现给出一些牛的数对(x, y)表示牛x认为牛y厉害。那么,求出所有的牛都认为该牛“厉害”的牛的个数。

题目分析

    牛之间的关系,形成一个有向图。其中存在一些强连通分支,若强连通分支内的一个牛被所有牛认为“厉害”,那么整个强连通分支内的牛都被认为“厉害”。因此,将强连通分支合并为一个点,对图重构。 
    重构后的图为一个简单的有向图,题目转换为寻找能从所有点均可达的点的数目(实际数目为点代表的强连通分支内的点数目之和)。使用定理有向无环图中出度为0的点,可以从任何出度不为0的点到达 
    因此,寻找该有向无环图中出度为0的点的个数,若出度为0的点的个数大于1,则这些出度为0的点之间互相不可达,则不存在所有点均可达的点;若出度为0的点的个数为1,则该出度为0的点代表的强连通分支内点的个数,即为题目的结果。

实现(c++)

#include<stdio.h>
#include<string.h>
#include<vector>
#include<stack>
#include<algorithm>

using namespace std;
#define MAX_NODE 10005
#define min(a, b) a < b? a:b

vector<int> gGraph[MAX_NODE];
stack<int> gStack;
bool gVisited[MAX_NODE];	//判断点是否被访问过
bool gInStack[MAX_NODE];	//判断点是否在栈中
int gDfn[MAX_NODE];			//在DFS过程中,点第一次被访问到的时间
int gLow[MAX_NODE];			//点x下方的点所能到达的序号最小的点的序号
int gIndex;

int gClusterIndex;				
int gClusterOfNode[MAX_NODE];	//每个点所属的强连通分支序号

//强连通分支结构体
struct Cluster{
	int cluster_id;
	int node_num;
	vector<int> linked_cluster;
	Cluster(int id, int num) :cluster_id(id), node_num(num){};
	bool LinkedCluster(int cluster){
		return find(linked_cluster.begin(), linked_cluster.end(), cluster) != linked_cluster.end();
	}
	void LinkCluster(int cluster){
		linked_cluster.push_back(cluster);
	}
	~Cluster(){
		linked_cluster.clear();
	}
};

vector<Cluster> gClusters;
//tarjan 算法求强连通分支
void Tarjan(int u){
	gDfn[u] = gLow[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] == false){
			Tarjan(v);
			gLow[u] = min(gLow[u], gLow[v]);
		}
		else if(gInStack[v]){ //注意,需要v在栈中才可以
			gLow[u] = min(gLow[u], gDfn[v]);
		}
	}
	if (gDfn[u] == gLow[u]){
		int v, num = 0;
		do{
			v = gStack.top();
			gClusterOfNode[v] = gClusterIndex;
			gStack.pop();
			gInStack[v] = false;		//注意恢复
			num++;
		} while (u != v);
		gClusters.push_back(Cluster(gClusterIndex, num));

		gClusterIndex++;
	}
}

//将强连通分支的各个点染色之后,再重新建图
void ReconstructGraph(int n){
	for (int u = 1; u <= n; u++){
		for (int j = 0; j < gGraph[u].size(); j++){
			int v = gGraph[u][j];
			int uc = gClusterOfNode[u];
			int vc = gClusterOfNode[v];
			if (uc != vc && !gClusters[uc].LinkedCluster(vc))
					gClusters[uc].LinkCluster(vc);
		}
	}
}
/*
int gRoot[MAX_NODE];
int GetRoot(int c){
	if (gRoot[c] != c){
		gRoot[c] = GetRoot(gRoot[c]);
	}
	return gRoot[c];
}
void Union(int c1, int c2){
	int p1 = GetRoot(c1);
	int p2 = GetRoot(c2);
	if (p1 != p2){
		gRoot[p1] = p2;
	}
}

bool DAG(){ //判断一个图是否为连通图,在此题中,可以不用判断
	int n = gClusters.size();

	for (int i = 0; i < n; i++){
		gRoot[i] = i;
	}

	for (int u = 0; u < n; u++){
		for (int i = 0; i < gClusters[u].linked_cluster.size(); i++){
			int v = gClusters[u].linked_cluster[i];
			Union(u, v);
		}
	}
	int r = GetRoot(0);
	for (int i = 1; i < n; i ++){ 
		if (r != GetRoot(i)){
			return false;
		}
	}
	return true;
}
*/
int main(){
	int n, m, u, v;
	while (scanf("%d %d", &n, &m) != EOF){
		for (int i = 0; i <= n; i++){
			gGraph[i].clear();
		}
		for (int i = 0; i < m; i++){
			scanf("%d %d", &u, &v);
			gGraph[u].push_back(v);
		}
		gIndex = 0;
		gClusterIndex = 0;
		memset(gInStack, false, sizeof(gInStack));
		memset(gVisited, false, sizeof(gVisited));
		gClusters.clear();

		//Tarjan 求强连通分支
		for (int i = 1; i <= n; i++){
			if (!gVisited[i]){
				Tarjan(i);
			}
		}

		//重新构图
		ReconstructGraph(n);

		/*
		if (!DAG()){
			printf("0\n");
			continue;
		}
		*/
		int zero_outdegree_cluster = 0, result = 0;
		for (int i = 0; i < gClusterIndex; i++){
			if (gClusters[i].linked_cluster.empty()){
				zero_outdegree_cluster++;
				result = gClusters[i].node_num;
			}
		}
		//若重构后的图中各个点不能构成一个连通图(将有向边变为无向边之后仍不能),那么就不存在一个点可以被其他所有点可达
		//而此时,图中也肯定存在多于1个点,其出度为0.
		//故,只需要判断重构后的图中,出度为0的点是否为1个即可。若出度为0的点有且只有一个,则返回该“点”(实际为一个强连通分支)
		//中的点的数目,否则,返回0

		if (zero_outdegree_cluster > 1){
			result = 0;
		}
		printf("%d\n", result);
	}
	return 0;
}

 

posted @ 2015-10-11 22:16  农民伯伯-Coding  阅读(268)  评论(0编辑  收藏  举报