目录
[蓝桥杯 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(2≤n≤1000), m ( 0 ≤ m ≤ 2000 ) m(0 \le m \le 2000) m(0≤m≤2000),分别代表站点数,通道数。
接下来 m m m 行,每行两个整数 u , v ( 1 ≤ u , v ≤ n , u ≠ v ) u,v(1 \le u,v \le n,u\neq v) u,v(1≤u,v≤n,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 1≤N,M≤103。
- 对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 1 0 5 1 \leq N,M \leq 10^5 1≤N,M≤105。
解题思路:
在读完题之后,顺着题的描述很容易想到可以对每个点进行一次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
1≤n≤104,
1
≤
m
≤
1
0
5
1\le m \le 10^5
1≤m≤105,保证没有重边。
解题思路:
首先分析题意,每条街都需要被封锁,即需要在一个端点上有河蟹,而两个河蟹不能相邻,故两个端点不能都是河蟹
那么题目大意就是图的每条边的两个端点状态是相异的,如果能实现则返回其中数目较小者,不能则输出“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;
}