洛谷每日一题(P3916 图的遍历)

原题目:图的遍历 - 洛谷

原题目截图:

思路分析:

一说到图的遍历,大家很容易就联想到BFS或者DFS算法,但是如果你试过这题,你就会发现这题有点小坑。我一开始很想当然的对每一个点进行BFS,这个思路最简单,但是显然数据量过大,案例会超时。然后我改成DFS方法,但是因为图中可能是有环路的图,因此当DFS的时候,有些边可能遍历不到。最后我想到了反向建图的BFS方法。

对每一个点进行BFS,案例超时,代码如下:


//对每个点进行正向BFS,部分案例超时
#include<iostream>
#include<vector>
#include<queue>
using namespace std;

struct edge {
    int u, v;
    bool flag = false; // 标记这条边是否访问过
};

int bfs(int first, vector<vector<int>>& graph, int n) {
    vector<int>visit(n + 1, 0);
    queue<int>que;
    que.push(first);
    int max = first;
    while (!que.empty()) {
        int cur = que.front();
        que.pop();
        visit[cur] = 1;
        max = max > cur ? max : cur;
        for (int next : graph[cur]) {
            if (visit[next] == 0) {
                que.push(next);
            }
        }
    }
    return max;

}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> graph(n + 1);
    vector<int> max(n + 1, 0); // 初始化 max 数组
    vector<int> visit(n + 1, 0); // 初始化访问标记数组

    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        graph[u].push_back(v);
    }

    for (int i = 1; i <= n; i++) { // 对每个节点调用 dfs
        if (!visit[i]) {
            max[i] = bfs(i, graph, n);
        }
    }

    for (int i = 1; i <= n; i++) {
        cout << max[i] << ' '; // 输出每个节点的最大可达节点编号
    }
    cout << endl;
    return 0;
}

反向建图+BFS算法思想:

思路的核心在于理解强连通图的性质,当我反向建图后,我从一个很大的点 x 出发,那么能够到达的其他点都是“本来可以抵达点 x 的点”。这样我们就有A[x能到达的其他点]=x。

  1. 初始化:创建一个图的邻接表表示,以及两个数组AA_visitA用于存储每个点能够到达的最大编号点,A_visit用于标记每个点 i 是否已经被计算过A[i],这样可以减少不必要的耗时。
  2. 逆序遍历:从编号最大的点开始,逆序遍历每个点。这样做的目的是为了确保在搜索过程中,如果一个点能够通过多个路径到达不同的最大编号点,我们总是先考虑编号最大的路径。
  3. 广度优先搜索:对于每个A_visit[i]==0的点,使用广度优先搜索从该点开始搜索,直到找到能够到达的最大编号点。在搜索过程中,记录每个点能够到达的最大编号点。
  4. 输出结果:最后,输出数组A的内容,即为每个点能够到达的最大编号点。
#include<iostream> // 引入输入输出流库
using namespace std; // 使用标准命名空间
#include<vector> // 引入向量库
#include<queue> // 引入队列库

// 定义BFS函数,用于从指定起点开始搜索能够到达的最大编号点
void bfs(vector<vector<int>>& graph, int start, int n,
	vector<int>& A, vector<int>& A_visit) {
	vector<bool>visit(n + 1, false); // 定义访问标记数组,初始化为未访问
	queue<int>que; // 定义队列,用于BFS
	que.push(start); // 将起点加入队列
	while (!que.empty()) { // 当队列不为空时循环
		int cur = que.front(); // 获取队列的第一个元素
		que.pop(); // 将该元素从队列中移除
		A[cur] = start; // 记录当前点能够到达的最大编号点为起点
		A_visit[cur] = 1; // 标记当前点已访问
		visit[cur] = true; // 更新访问标记数组
		for (int point : graph[cur]) { // 遍历当前点的所有邻接点
			if (visit[point] == false && A_visit[point] == 0) que.push(point); // 如果邻接点未被访问并且没有计算过A[point],则加入队列
		}
	}
	return; // 返回
}

// 定义函数,用于打印数组A的内容
void printA(vector<int>A) {
	for (int i = 1; i < A.size(); i++) { // 遍历数组A
		cout << A[i] << " "; // 打印每个元素
	}

	return; // 返回
}

int main() {
	int n, m; // 定义点数和边数
	cin >> n >> m; // 读取点数和边数
	vector<vector<int>>graph(n + 1); // 定义图的邻接表
	vector<int>A_visit(n + 1, 0); // 定义访问标记数组,用于记录点i是否被计算过A[i]
	vector<int>A(n + 1); // 定义数组A,用于存储每个点能够到达的最大编号点

	for (int i = 0; i < m; i++) { // 遍历每条边
		int ui, vi; // 定义边的两个端点
		cin >> ui >> vi; // 读取边的两个端点
		graph[vi].push_back(ui); // 在邻接表中添加边,注意这里是反向添加,因为题目要求从每个点出发
	}

	for (int i = 1; i <= n; i++) { // 初始化数组A
		A[i] = i; // 将每个点初始化为能够到达的最大编号点是其自身
	}

	for (int i = n; i > 0; i--) { // 从最大的点开始,逆序遍历每个点
		if (A_visit[i] == 0) // 如果该点未被访问过
			bfs(graph, i, n, A, A_visit); // 从该点开始进行BFS
	}

	printA(A); // 打印数组A的内容

	return 0; // 返回0,表示程序正常结束
}

今天的博客就写到这里了,与诸君共勉之!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值