1. 图的遍历算法解析与洛谷3916题实战
刚接触图论算法时,很多人会被各种遍历方式绕晕。今天我们就以洛谷经典题目P3916为例,拆解图的遍历核心逻辑,分享从理论到AC的完整路径。这道题虽然标为"普及-"难度,但涉及的反向建图技巧和遍历优化,在实际工程中也非常实用。
先看题目要求:给定N个顶点、M条边的有向图,对每个顶点V,求从V出发能到达的最大顶点编号。数据范围是1≤N≤10^5,这意味着O(N^2)的暴力解法必然超时。接下来我会带你用两种时间复杂度O(N+M)的方案攻破此题,并分析各自的适用场景。
1.1 图的存储结构选择
面对图论问题,首先需要根据题目特性选择存储结构。邻接矩阵显然不适合本题——10^5的顶点数会导致10^10量级的空间消耗。我们采用更通用的邻接表,用C++ vector实现如下:
cpp复制vector<int> graph[MAX_N];
但这里有个关键细节:常规邻接表存储的是正向边(u→v),而本题采用反向建表(v→u)能大幅提升效率。这是因为题目要求的是"能到达的最大顶点",反向遍历时,一旦访问到某个顶点,其最大可达值就立即确定。这种逆向思维在解决可达性问题时非常有效。
1.2 反向DFS实现方案
我们先实现反向DFS版本。核心思路是:从编号最大的顶点开始倒序DFS,每个顶点的答案就是其所在连通分量的最大编号顶点。
cpp复制void dfs(int u, int max_val) {
if (ans[u] != 0) return; // 已访问
ans[u] = max_val;
for (int v : reverse_graph[u]) {
dfs(v, max_val);
}
}
// 调用方式
for (int i = N; i >= 1; i--) {
dfs(i, i);
}
这个实现有几个优化点:
- 使用全局ans数组避免重复计算
- 倒序访问确保每个连通分量只处理一次
- 递归实现代码简洁但需要注意栈溢出风险
提示:当N>1e5时,递归DFS可能导致栈溢出。可以用显式栈实现迭代版DFS,或者调整编译器栈大小(如GCC的-Wl,--stack=268435456)
