(Week 5)图(C++,DFS,BFS)

[蓝桥杯 2013 国 C] 危险系数(图,DFS,C++)

题目背景

抗日战争时期,冀中平原的地道战曾发挥重要作用。

题目描述

地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。

我们来定义一个危险系数 D F ( x , y ) DF(x,y) DF(x,y)

对于两个站点 x x x y ( x ≠ y ) , y(x\neq y), y(x=y), 如果能找到一个站点 z z z,当 z z z 被敌人破坏后, x x x y y y 不连通,那么我们称 z z z 为关于 x , y x,y x,y 的关键点。相应的,对于任意一对站点 x x x y y y,危险系数 D F ( x , y ) DF(x,y) DF(x,y) 就表示为这两点之间的关键点个数。

本题的任务是:已知网络结构,求两站点之间的危险系数。

输入格式

输入数据第一行包含 2 2 2 个整数 n ( 2 ≤ n ≤ 1000 ) n(2 \le n \le 1000) n(2n1000) m ( 0 ≤ m ≤ 2000 ) m(0 \le m \le 2000) m(0m2000),分别代表站点数,通道数。

接下来 m m m 行,每行两个整数 u , v ( 1 ≤ u , v ≤ n , u ≠ v ) u,v(1 \le u,v \le n,u\neq v) u,v(1u,vn,u=v) 代表一条通道。

最后 1 1 1 行,两个数 u , v u,v u,v,代表询问两点之间的危险系数 D F ( u , v ) DF(u,v) DF(u,v)

输出格式

一个整数,如果询问的两点不连通则输出 − 1 -1 1

样例 #1

样例输入 #1

7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6

样例输出 #1

2

提示

时限 1 秒, 64M。蓝桥杯 2013 年第四届国赛

解题思路

本题的解题思路就是找出所有从u到v的路径,累计每个节点在路径中出现的次数,若二者相等,则该节点为关键节点

首先我们先利用vector实现存图的操作

#include <iostream>
using namespace std;

int n, m;//点数、边数
vector<int> nodes[max_n];//下标为点,两个下标确定一条有向边

int main()
{
	int temp_point_1, temp_point_2;
	cin >> n >> m;
	for (int i = 0; i < m; i++)//读入m条边
	{
		cin >> temp_point_1 >> temp_point_2;
		nodes[temp_point_1].push_back(temp_point_2);//从1到2
		nodes[temp_point_2].push_back(temp_point_1);//从2到1
	}
}

然后,既然要遍历每一条路径,那么我们采用深度优先搜索

void depth_first_search(int now)
{
	//终止条件
	if (now == ed)
	{
		path_sum++;//累计路径数
		for (int i = 1; i <= n; i++)//累计点出现的次数
		{
			if (book_nodes[i])
			{
				nodes_sum[i]++;
			}
		}
		return;
	}
	//递归主体
	for (int i = 0; i < int(nodes[now].size()); i++)//从now开始尝试每一个能到达的点
	{
		if (!book_nodes[nodes[now][i]])//如果未到达过
		{
			book_nodes[nodes[now][i]] = true;//标记
			depth_first_search(nodes[now][i]);
			book_nodes[nodes[now][i]] = false;//取消标记
		}
	}
	return;
}

本题基本实现完毕,完整的代码如下

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

const int max_n = 1000 + 1;

vector<int> nodes[max_n];
bool book_nodes[max_n] = { false };
int sta, ed, n, m;
int path_sum = 0;
int nodes_sum[max_n] = { 0 };

void depth_first_search(int now)
{
	//终止条件
	if (now == ed)
	{
		path_sum++;
		for (int i = 1; i <= n; i++)
		{
			if (book_nodes[i])
			{
				nodes_sum[i]++;
			}
		}
		return;
	}
	//递归主体
	for (int i = 0; i < int(nodes[now].size()); i++)
	{
		if (!book_nodes[nodes[now][i]])
		{
			book_nodes[nodes[now][i]] = true;
			depth_first_search(nodes[now][i]);
			book_nodes[nodes[now][i]] = false;
		}
	}
	return;
}

int main()
{
	int temp_point_1, temp_point_2, danger = 0;
	cin >> n >> m;
	for (int i = 0; i < m; i++)
	{
		cin >> temp_point_1 >> temp_point_2;
		nodes[temp_point_1].push_back(temp_point_2);
		nodes[temp_point_2].push_back(temp_point_1);
	}
	cin >> sta >> ed;
	book_nodes[sta] = true;//注意要标记起点
	depth_first_search(sta);
	book_nodes[sta] = false;
	if (path_sum != 0)//如果u,v之间连通
	{
		for (int i = 1; i <= n; i++)//统计关键节点数量
		{
			if (path_sum == nodes_sum[i])
			{
				danger++;
			}
		}
		cout << danger - 2 << endl;//减去起点和终点,输出
	}
	else
	{
		cout << -1 << endl;
	}
	return 0;
}

图的遍历(C++,BFS)

题目描述

给出 N N N 个点, M M M 条边的有向图,对于每个点 v v v,求 A ( v ) A(v) A(v) 表示从点 v v v 出发,能到达的编号最大的点。

输入格式

1 1 1 2 2 2 个整数 N , M N,M N,M,表示点数和边数。

接下来 M M M 行,每行 2 2 2 个整数 U i , V i U_i,V_i Ui,Vi,表示边 ( U i , V i ) (U_i,V_i) (Ui,Vi)。点用 1 , 2 , … , N 1,2,\dots,N 1,2,,N 编号。

输出格式

一行 N N N 个整数 A ( 1 ) , A ( 2 ) , … , A ( N ) A(1),A(2),\dots,A(N) A(1),A(2),,A(N)

样例 #1

样例输入 #1

4 3
1 2
2 4
4 3

样例输出 #1

4 4 3 4

提示

  • 对于 60 % 60\% 60% 的数据, 1 ≤ N , M ≤ 1 0 3 1 \leq N,M \leq 10^3 1N,M103
  • 对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 1 0 5 1 \leq N,M \leq 10^5 1N,M105

解题思路:

在读完题之后,顺着题的描述很容易想到可以对每个点进行一次bfs来寻找最大值

显然,这道题不会这么简单

如果这个时候仍不能换个角度考虑的话,那么我们会想到

如果能够到达一个已经bfs过的点,那么就能到达它所能到达的所有点,直接将这些点标记并返回最大值即可

然后优化之后发现还是会TLE

那怎么办呢

这时我们可以运用逆向思维,与其考虑能够到达的最大值点,不如直接考虑最大值点能到达哪些点

那么思路一下就清晰起来了

首先是存图的操作,用vector存图,同时要注意将有向边反向

int main()
{
	int n, m, tail, head;
	cin >> n >> m;
	for (int i = 0; i < m; i++)
	{
		cin >> tail >> head;
		map[head].push_back(tail);//反向存储有向边
	}
	return 0;

}

然后就是从最大值点开始依次进行bfs

int main()
{
	int n, m, tail, head;
	cin >> n >> m;
	for (int i = 0; i < m; i++)
	{
		cin >> tail >> head;
		map[head].push_back(tail);
	}
	return 0;
	for (int i = n; i >= 1; i--)
	{
		if (!(book[i]))//注意已经到访问的节点无需再次访问
		{
			bfs(i);
		}
	}
}

以下是bfs()的实现

const int max_n = int(1e5) + 1;

vector<int> map[max_n];//存图,下标表示节点
queue<int> bfs_queue;//bfs队列
bool book[max_n] = { false };//节点标记物,下标表示节点
int max_id[max_n] = { 0 };//数据处理结果,下标表示节点

void bfs(int now)//now为最大值
{
	bfs_queue.push(now);
	book[now] = true;//标记起点
	max_id[now] = now;
	while (!(bfs_queue.empty()))
	{
		for (int i = 0; i < int(map[bfs_queue.front()].size()); i++)//访问从当前队首元素所能访问的所有节点
		{
			if(!(book[map[bfs_queue.front()][i]]))
			{
				bfs_queue.push(map[bfs_queue.front()][i]);
				book[map[bfs_queue.front()][i]] = true;//注意已经访问过的点要标记,且无需取消标记
				max_id[map[bfs_queue.front()][i]] = now;//所有能访问的节点最大值均为now
			}
		}
		bfs_queue.pop();//队首节点尝试完毕,出队
	}
}

完整的代码实现如下

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

const int max_n = int(1e5) + 1;

vector<int> map[max_n];
queue<int> bfs_queue;
bool book[max_n] = { false };
int max_id[max_n] = { 0 };

void bfs(int now)
{
	bfs_queue.push(now);
	book[now] = true;
	max_id[now] = now;
	while (!(bfs_queue.empty()))
	{
		for (int i = 0; i < int(map[bfs_queue.front()].size()); i++)
		{
			if(!(book[map[bfs_queue.front()][i]]))
			{
				bfs_queue.push(map[bfs_queue.front()][i]);
				book[map[bfs_queue.front()][i]] = true;
				max_id[map[bfs_queue.front()][i]] = now;
			}
		}
		bfs_queue.pop();
	}
}

int main()
{
	int n, m, tail, head;
	cin >> n >> m;
	for (int i = 0; i < m; i++)
	{
		cin >> tail >> head;
		map[head].push_back(tail);
	}
	for (int i = n; i >= 1; i--)
	{
		if (!(book[i]))
		{
			bfs(i);
		}
	}
	for (int i = 1; i < n; i++)
	{
		cout << max_id[i] << ' ';
	}
	cout << max_id[n];
	return 0;
}


封锁阳光大学(C++,图,染色法)

题目描述

曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹,感到不爽。河蟹决定封锁阳光大学,不让曹刷街。

阳光大学的校园是一张由 n n n 个点构成的无向图, n n n 个点之间由 m m m 条道路连接。每只河蟹可以对一个点进行封锁,当某个点被封锁后,与这个点相连的道路就被封锁了,曹就无法在这些道路上刷街了。非常悲剧的一点是,河蟹是一种不和谐的生物,当两只河蟹封锁了相邻的两个点时,他们会发生冲突。

询问:最少需要多少只河蟹,可以封锁所有道路并且不发生冲突。

输入格式

第一行两个正整数,表示节点数和边数。
接下来 m m m 行,每行两个整数 u , v u,v u,v,表示点 u u u 到点 v v v 之间有道路相连。

输出格式

仅一行如果河蟹无法封锁所有道路,则输出 Impossible,否则输出一个整数,表示最少需要多少只河蟹。

样例 #1

样例输入 #1

3 3
1 2
1 3
2 3

样例输出 #1

Impossible

样例 #2

样例输入 #2

3 2
1 2
2 3

样例输出 #2

1

提示

【数据规模】
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 4 1\le n \le 10^4 1n104 1 ≤ m ≤ 1 0 5 1\le m \le 10^5 1m105,保证没有重边。

解题思路:

首先分析题意,每条街都需要被封锁,即需要在一个端点上有河蟹,而两个河蟹不能相邻,故两个端点不能都是河蟹

那么题目大意就是图的每条边的两个端点状态是相异的,如果能实现则返回其中数目较小者,不能则输出“Impossible”

那么我们可以用染色法对图进行染色从而解决本题

测试样例给出的图不一定连通,所以我们需要处理每一个子图

实现代码如下

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

vector<int>map[int(1e5) + 1];//图
queue<int>bfs_queue;
bool book[int(1e5) + 1] = { 0 };//标记物
bool paint_book[int(1e5) + 1] = { 0 };//染色

int bfs(int start) {//染色法
	int white_sum = 0, black_sum = 0;//统计白、黑节点的数量
	bfs_queue.push(start);
	book[start] = true;
	paint_book[start] = 1;//1为白色,0为黑色
	white_sum += 1;
	while (!(bfs_queue.empty())) {
		for (int i = 0; i < map[bfs_queue.front()].size(); i++) {
			if (!book[map[bfs_queue.front()][i]]) {//判断是否访问过

				bfs_queue.push(map[bfs_queue.front()][i]);
				book[map[bfs_queue.front()][i]] = true;

				if (paint_book[bfs_queue.front()]) {//队首节点为白色
					paint_book[map[bfs_queue.front()][i]] = 0;
					black_sum += 1;
				}
				else {//队首节点为黑色
					paint_book[map[bfs_queue.front()][i]] = 1;
					white_sum += 1;
				}
			}
			else {//访问过则判断颜色是否相异
				if (paint_book[bfs_queue.front()] == paint_book[map[bfs_queue.front()][i]])//相同
					return -1;//impossible
			}
		}
		bfs_queue.pop();
	}
	if (white_sum > black_sum)
		return black_sum;
	else
		return white_sum;
}

int main(){
	int n, m, sum = 0, u, v;//节点数、边数、河蟹总数、端点
	cin >> n >> m;

	for (int i = 0; i < m; i++){//存图
		cin >> u >> v;
		map[u].push_back(v);
		map[v].push_back(u);
	}

	for (int i = 1; i <= n; i++) {
		if (!(book[i])) {
			int ret = bfs(i);
			if (ret == -1) {
				cout << "Impossible";
				return 0;
			}
			else {
				sum += ret;
			}
		}
		
	}
	cout << sum;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WitheredSakura_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值