CCF-201509-4(高速公路)

一:问题
1.问题描述

某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路。
  现在,大臣们帮国王拟了一个修高速公路的计划。看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能。如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对。
  国王想知道,在大臣们给他的计划中,有多少个便利城市对。

2.格式

输入格式
  输入的第一行包含两个整数n, m,分别表示城市和单向高速公路的数量。
  接下来m行,每行两个整数a, b,表示城市a有一条单向的高速公路连向城市b。
输出格式
  输出一行,包含一个整数,表示便利城市对的数量。

3.样例

样例输入
5 5
1 2
2 3
3 4
4 2
3 5
样例输出
3
样例说明

 城市间的连接如图所示。有3个便利城市对,它们分别是(2, 3), (2, 4), (3, 4),请注意(2, 3)和(3, 2)看成同一个便利城市对。

4.评测用例规模与约定

前30%的评测用例满足1 ≤ n ≤ 100, 1 ≤ m ≤ 1000;
前60%的评测用例满足1 ≤ n ≤ 1000, 1 ≤ m ≤ 10000;
所有评测用例满足1 ≤ n ≤ 10000, 1 ≤ m ≤ 100000。

二:理解

思路:

在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。

求回路也就是求图的强连通分量
共有两种算法:Kosaraju与tarjan,其中Kosaraju算法较易理解但需要两次dfs,tarjan算法较难理解但只需dfs一次。

核心思想:一个强连通分量中任意两个节点一定是互相可达的,那么这个强连通分量的从i到j的路径构成一个环(when i=j),因此我们的核心思想就是寻找环。

tarjan算法:

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

首先,这个算法是基于深度优先搜索的(DFS),你必须理解图的深度优先搜索流程,然后我们需要明白在一个强连通分量中,任何两个点都是可达的,那么,你在对图进行深度优先搜索的时候,如果出现了环,是不是就意味着你找到了一个连通分量,因为环中的任何两个节点都是可达的,实际上,这基本就是Tarjan算法的核心思想,找环。

注:着实看不懂这个代码是什么意思,没看懂。

Kosaraju算法:

1、对图的反向图进行一次逆后序dfs遍历
2、按照逆后序遍历得到的栈中点的出栈顺序对原图进行dfs遍历,每一次遍历访问到的点就属于一个强连通分量。

两者对比:

运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。
求有向图的强连通分量还有一个强有力的算法,为Kosaraju算法。Kosaraju是基于对有向图及其逆图两次DFS的方法,其时间复杂度也是O(N+M)。与Trajan算法相比,Kosaraju算法可能会稍微更直观一些。但是Tarjan只用对原图进行一次DFS,不用建立逆图,更简洁。在实际的测试中,Tarjan算法的运行效率也比Kosaraju算法高30%左右。

代码理解:
数据结构:

vector<int>Map1[10005];  //正向存储边与点的信息 
vector<int>Map2[10005];  //反向存储边与点的信息 
bool visited[10005];  //标记是否遍历过
stack<int> p;  //存储逆向的点编号
int counts = 0int num = 0;  //记录连续的强联通分量的点的个数

1.按着逆方向的形式,将每个点入栈

void DFS1(int now)  //倒序深搜 
{
    
    
	for(int i=0; i<Map2[now].size(); i++)
	{
    
    
		int temp = Map2[now][i];
		if(!visited[temp])
		{
    
    
			visited[temp] = 1;
			DFS1(temp);
		}
	}
	p.push(now);
}

memset(visited, false, sizeof(visited));
for(int i=n; i>=1; i--)
{
    
    
	if(!visited[i])
	{
    
    
		visited[i] = 1;
		DFS1(i);
	}
}

2.将队列中的点,正方向找连续的强联通分量的点的个数,

void DFS2(int now)
{
    
    
	for(int i=0; i<Map1[now].size(); i++)
	{
    
    
		int temp = Map1[now][i];
		if(!visited[temp])
		{
    
    
			visited[temp] = 1;
			num++;
			DFS2(temp);
		}
	}
}

memset(visited, false, sizeof(visited));
while(!p.empty())
{
    
    
	int temp = p.top();
	p.pop();
	if(!visited[temp])
	{
    
    
		visited[temp] = 1;
		num = 1;
		DFS2(temp);
		counts += calc(num);
	}
}

3.然后再计算出强联通分量的个数。

int  calc(int x)  //计算强量通分量 
{
    
    
	if(x <= 1)
		return 0;
	else 
		return x*(x-1)/2;	
}

三:代码
Kosaraju算法代码:

//Kosaraju
#include<bits/stdc++.h>
using namespace std;

int n, m;
vector<int>Map1[10005];  //正向存储边与点的信息 
vector<int>Map2[10005];  //反向存储边与点的信息 
bool visited[10005];  //标记是否遍历过
stack<int> p;  //存储逆向的点编号
int counts = 0int num = 0;  //记录连续的强联通分量的点的个数

void DFS1(int now)  //倒序深搜 
{
    
    
	for(int i=0; i<Map2[now].size(); i++)
	{
    
    
		int temp = Map2[now][i];
		if(!visited[temp])
		{
    
    
			visited[temp] = 1;
			DFS1(temp);
		}
	}
	p.push(now);
}
void DFS2(int now)
{
    
    
	for(int i=0; i<Map1[now].size(); i++)
	{
    
    
		int temp = Map1[now][i];
		if(!visited[temp])
		{
    
    
			visited[temp] = 1;
			num++;
			DFS2(temp);
		}
	}
}
int  calc(int x)  //计算强量通分量 
{
    
    
	if(x <= 1)
		return 0;
	else 
		return x*(x-1)/2;	
}
int main()
{
    
    
	cin >> n >> m;
	//初始化正序与逆序的信息 
	for(int i=0; i<m; i++)
	{
    
    
		int a, b;
		cin >> a >> b;
		Map1[a].push_back(b);  //表明a点可以到达b点 
		Map2[b].push_back(a); 
	}  
	//倒序存在栈中 
	memset(visited, false, sizeof(visited));
	for(int i=n; i>=1; i--)
	{
    
    
		if(!visited[i])
		{
    
    
			visited[i] = 1;
			DFS1(i);
		}
	}
	
	memset(visited, false, sizeof(visited));
	while(!p.empty())
	{
    
    
		int temp = p.top();
		p.pop();
		if(!visited[temp])
		{
    
    
			visited[temp] = 1;
			num = 1;
			DFS2(temp);
			counts += calc(num);
		}
	}
	cout << counts << endl; 
	return 0;
}

猜你喜欢

转载自blog.csdn.net/pfl_327/article/details/104803214
今日推荐