poj-2186 受欢迎的奶牛(tarjan算法应用)

题意:有n只奶牛,奶牛之间有倾慕的关系,并且倾慕关系是可以传递的。要求找出某几个奶牛,这几个奶牛被所有的奶牛喜欢。


奶牛的关系抽象成为有向图,a喜欢b表示为a点到b点有一条有向边。


思路:

1 通过tarjan算法,求出强连通分量,并且把这些强连通分量缩成缩点。通过这种方法把图转化为有向无环图。

2 在这个有向无环图中,如果存在唯一一个点A,这个点的出度为0,有向无环图中所有的点都有路径到达A。A点所代表的强连通分量中的点就是被所有奶牛喜欢的受欢迎奶牛。


思路详解:tarjan算法的详解以及原理在博客  tarjan算法原理介绍中,点击打开链接,计算缩点是先在tarjan算法出栈scc(强连通分量)的时候记录下每个点的scc编号,然后重新生成一张图。

思路中的步骤2证明如下:

一个有向图,不包含环路。如果有且只有一个点A的出度为0,则任意一个点都有一条路径到A

运用数学归纳法证明:

当只有点AB,且有B->A这一条路径,结论成立。

假设有n个节点和m条有向边,保证没有环路且只有A出度为零,任意一个点都有一条路径到A

加入一个节点Z和任意几条有向边,保证没有环路且只有A出度为零(Z必然有一个出度连向2中的某个节点)。那么在2的基础上,Z可以到达2中的某个节点,而2中每个节点都有一条路径到A,所以Z也有一条路径到A

 

综上所述,结论成立。



#include<iostream> 
#include<string>
#include<iomanip>
#include<vector>
using namespace std;

typedef struct node_arr_elem{
	int ID;
	vector<int> out_adj_node;
} node_arr_elem;
vector<node_arr_elem> adj_list, scc_adj; 

int pairs[100][2];
int scc_belonging[10000];//记录每个点属于的强连通分量 

int cmp(int index_1, int index_2){
	if(pairs[index_1][0]>pairs[index_2][0]){
		return 1;//>
	} else if(pairs[index_1][0]<pairs[index_2][0]){
		return -1;//<
	} else{
		if(pairs[index_1][1]>pairs[index_2][1]){
			return 1;//>
		} else if(pairs[index_1][1]<pairs[index_2][1]){
			return -1;//<
		} else{
			return 0;//=
		}
	}
}

void sort(int h, int l){
	int i,j,temp[2];
	if(h>l){
		temp[0] = pairs[l][0]; temp[1] = pairs[l][1];
		int state = 0;
		for(i=l,j=h;i<j;){
			if(state == 0){
				if(cmp(j,l)==-1){
					state = 1;
					pairs[i][0]=pairs[j][0];
					pairs[i][1]=pairs[j][1];
				} else{
					--j;
				}	
			}
			if(state == 1){
				if(cmp(i,l)==1){
					state = 0;
					pairs[j][0]=pairs[i][0];
					pairs[j][1]=pairs[i][1];
				} else{
					++i;
				}
			}
		}
		pairs[i][0] = temp[0];
		pairs[i][1] = temp[1];
		sort(l,i-1);
		sort(i+1,h);
	}
}
int delete_repeat(int num){
	sort(0,num-1);
	/*
	for(int i=0; i<num; ++i){
		cout<<pairs[i][0]<<"-"<<pairs[i][1]<<endl;
	}
	cout<<endl;*/
	
	int j=1,states = 0;
	for(int i=1;i<num;){
		if(pairs[i][0]==pairs[i-1][0]&&pairs[i][1]==pairs[i-1][1]){
			++i;
		}
		if(pairs[i][0]!=pairs[i-1][0]||pairs[i][1]!=pairs[i-1][1]){
			pairs[j][0] = pairs[i][0];
			pairs[j][1] = pairs[i][1];
			++j;++i;

		}
	}
	int new_length = j-1;
	return new_length; 
}

void create_graph(int n,int m,int choice){//生成图 
	
	for(int i=0; i<n; ++i){
		node_arr_elem new_n;
		new_n.ID = i+1;
		if(choice == 0)
			adj_list.push_back(new_n);
		else
			scc_adj.push_back(new_n);
	}
	for(int i=0; i<m; ++i){
		if(pairs[i][0]!=pairs[i][1]){
			if(choice == 0)
				adj_list[pairs[i][0]].out_adj_node.push_back(pairs[i][1]);
			else
				scc_adj[pairs[i][0]].out_adj_node.push_back(pairs[i][1]);
		}
	}
}

/*tarjan算法*/
vector<int> stack;
int low[10000] = {0};
int dfn[10000] = {0}; 
int time_stamp = 1;
int scc_count = 0;
int tarjan(int index){
	dfn[index] = low[index] = time_stamp++;
	stack.push_back(index);
	for(int i=0; i<adj_list[index].out_adj_node.size(); ++i){
		int v = adj_list[index].out_adj_node[i];
		if(find(stack.begin(),stack.end(),v) != stack.end()){
			low[index] = dfn[v]>low[index] ? low[index] : dfn[v];
		} else if(dfn[v] == 0){
			low[v] = tarjan(v);
			low[index] = low[v]>low[index] ? low[index] : low[v];
		}
	}
	if( dfn[index] == low[index] ){//发现强连通分量 
		//strong_connection s_c;
		int node;
		//connection_parts.push_back(s_c); 
		while( stack[stack.size()-1] != index ){
			node = stack[stack.size()-1]; 
			//connection_parts[connection_parts.size()-1].nodes.push_back(node);
			scc_belonging[node] = scc_count;//缩点,记录下这个点所属于的强连通分量 
			stack.pop_back();
		}
		node = stack[stack.size()-1]; 
		//connection_parts[connection_parts.size()-1].nodes.push_back(node);
		scc_belonging[node] = scc_count;
		stack.pop_back();
		
		++scc_count;
	}
	return low[index];
}
/*tarjan算法完*/

int main(){
	int n, m;
	cin>>n>>m;
	for(int i=0; i<m; ++i){
		cin>>pairs[i][0]>>pairs[i][1];
		--pairs[i][0];
		--pairs[i][1];
	}
	
	create_graph(n,m,0);//生成图 
	
	//求强连通分量 并 缩点 
	for(int finish=0; finish==0;){
		int j=-1;
		for(int i=0; i<n; ++i){
			if(dfn[i]==0){
				j=i;
			}
		}
		if(j!=-1){
			tarjan(j);//求强连通分量 
		} else{
			finish = 1;
		}
	}
	
	for(int i=0; i<m; ++i){
		pairs[i][0] = scc_belonging[pairs[i][0]];
		pairs[i][1] = scc_belonging[pairs[i][1]];
	}
	
	int new_m = delete_repeat(m);
	create_graph(scc_count,new_m,1);

	int num=0,scc_no;
	for(int i=0; i<scc_count; ++i){
		if(scc_adj[i].out_adj_node.size()==0){
			scc_no = i;
			++num;
		}
	}

	if(num==1){
		for(int i=0; i<n; ++i){
			if(scc_belonging[i] == scc_no){
				cout<<i+1<<" ";
			}
		}
	} else{
		cout<<"no";
	}
	
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值