P3916 图的遍历
题目理解
这道题目要求我们对一个有向图进行反向遍历,对于每个节点v,找出从v出发能够到达的编号最大的节点。题目中的关键点包括:
- 
图的构建:给定的有向边关系构成一个有向图 
- 
遍历目标:对于每个节点,找到它能到达的最大编号节点 
- 
输出要求:输出每个节点对应的最大可达节点编号 
解题思路
1. 图的表示
使用邻接表(vector<int> g[N])来存储图的边关系,但是这里采用了反向建图的方式:
- 
原始边是u→v,但我们存储为v→u 
- 
这样可以从大编号节点开始遍历,利用贪心思想优化效率 
2. 反向DFS策略
核心思想是从编号最大的节点开始反向DFS:
- 
按编号从大到小遍历每个节点 
- 
对于每个未处理的节点i,进行DFS 
- 
在DFS过程中,标记所有能到达i的节点,并将它们的res值设为i 
- 
这样确保每个节点的res值是其能到达的最大编号 
3. 贪心优化
- 
由于我们从大编号开始处理,一旦一个节点被标记,它的res值就已经是当前能得到的最大值 
- 
这样可以避免重复处理,提高效率 
代码注释详解
#include<bits/stdc++.h>
#define N 100005 // 定义最大节点数
#define endl "\n"
using namespace std;
int n, m, t; // n:节点数, m:边数, t:当前处理的最大编号
vector<int> g[N]; // 反向邻接表存储图
int vis[N]; // 访问标记数组
int res[N]; // 存储结果的数组
// 深度优先搜索
void dfs(int x) {
    vis[x] = 1; // 标记当前节点已访问
    for(int v : g[x]) { // 遍历所有邻接节点
        if(vis[v] == 0 && res[v] == 0) { // 如果邻接节点未被访问且未被处理
            res[v] = t; // 设置结果为当前最大编号t
            dfs(v); // 递归访问
        }
    }
}
int main() {
    cin >> n >> m; // 输入节点数和边数
    while(m--) { // 处理每条边
        int u, v;
        scanf("%d %d", &u, &v); // 输入边u→v
        g[v].push_back(u); // 反向存储边v→u
    }
    // 从大编号到小编号依次处理
    for(int i = n; i >= 1; i--) {
        t = i; // 当前处理的最大编号
        if(res[i] == 0) { // 如果该节点尚未处理
            memset(vis, 0, sizeof(vis)); // 重置访问标记
            dfs(i); // 进行DFS
        }
        res[i] = max(res[i], t); // 确保自身也被处理
    }
    // 输出结果
    for(int i=1; i<=n; i++) 
        printf("%d ", res[i]);
    return 0;
}复杂度分析
- 
时间复杂度: - 
反向建图:O(m) 
- 
DFS处理:每个节点和边最多被访问一次,O(n + m) 
- 
总体:O(n + m) 
 
- 
- 
空间复杂度: - 
邻接表存储:O(n + m) 
- 
访问数组和结果数组:O(n) 
 
- 
总结
这道题目考察了图的遍历算法和贪心思想的应用。关键点在于:
- 
反向建图的巧妙设计 
- 
从大编号开始处理的贪心策略 
- 
通过DFS标记所有能到达当前最大编号的节点 
- 
避免重复处理以提高效率 
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号