intmain(){ int n, m; cin >> n >> m; vector<vector<int>> adj_list(n + 1); for (int k = 0; k < m; k++) { int i, j; cin >> i >> j; adj_list[i].push_back(j); adj_list[j].push_back(i); } vector<bool> visited(n + 1, false); int gang_count = 0; for (int i = 1; i <= n; i++) { if (!visited[i]) { gang_count++; dfs(i, adj_list, visited); } } cout << gang_count << endl; return0; }
int n, m; cin >> n >> m; // 罪犯编号为 1 到 n,因此开辟大小为 n + 1 的数组以对齐索引 vector<vector<int>> adj_list(n + 1); for (int k = 0; k < m; k++) { int i, j; cin >> i >> j; adj_list[i].push_back(j); // 添加正向边:i 认识 j adj_list[j].push_back(i); // 添加反向边:j 认识 i(无向图特征) }
解析:由于题目中罪犯的编号是从 1 到 n,为了代码可读性与防越界,邻接表的大小被设定为 n + 1。因为“认识”是双向关系,所以每读入一条边,都需要在两个结点的邻接链表中分别执行 push_back()。
3. 连通分量计数主循环
1 2 3 4 5 6 7 8 9 10 11
vector<bool> visited(n + 1, false); // 同样开辟 n + 1 大小的标记数组 int gang_count = 0; // 犯罪团伙(连通分量)计数器 // 从编号 1 到 n 依次遍历 for (int i = 1; i <= n; i++) { // 如果该罪犯未被归入任何团伙 if (!visited[i]) { gang_count++; // 发现新团伙,计数加 1 dfs(i, adj_list, visited); // 通过 DFS 将该团伙的所有成员一网打尽 } } cout << gang_count << endl; // 输出最终的团伙数量
voiddfs(int x, int y, int m, int n, vector<string>& grid){ grid[x][y] = '*'; for (int i = 0; i < 8; i++) { int nx = x + dx[i]; int ny = y + dy[i]; if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == '@') { dfs(nx, ny, m, n, grid); } } }
intmain(){ int m, n; while (cin >> m >> n && m != 0) { vector<string> grid(m); for (int i = 0; i < m; i++) { cin >> grid[i]; } int oil_fields = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == '@') { oil_fields++; dfs(i, j, m, n, grid); } } } cout << oil_fields << endl; } return0; }
voiddfs(int x, int y, int m, int n, vector<string>& grid){ grid[x][y] = '*'; // 原地修改:将当前 '@' 改为 '*',避免引入额外的 visited 空间 // 循环遍历当前格子的 8 个邻居 for (int i = 0; i < 8; i++) { int nx = x + dx[i]; // 计算邻居的行坐标 int ny = y + dy[i]; // 计算邻居的列坐标 // 确保边界合法,并且该邻居也是尚未处理的油田 pocket if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == '@') { dfs(nx, ny, m, n, grid); // 递归扩散 } } }
解析:在网格图像搜索中,定义 dx 和 dy 数组可以大幅简化代码结构。这里使用的是“原地标记法”,直接将遍历过的 @ 字符修改为 *,既能防止重复访问导致的死循环,又消除了空间开销。
2. 多组数据读入处理
1 2 3 4 5 6 7
int m, n; // 循环读取数据,直到遇到 m 为 0 时终止退出 while (cin >> m >> n && m != 0) { vector<string> grid(m); for (int i = 0; i < m; i++) { cin >> grid[i]; // 读入长度为 n 的字符串,作为网格的一行 }
解析:本块实现了多组测试数据的安全读入。通过 while (cin >> m >> n && m != 0) 结构,能够连续处理多个不同的地图网格,直至接收到停机信号。
3. 全局扫描与结果输出
1 2 3 4 5 6 7 8 9 10 11 12 13
int oil_fields = 0; // 记录当前网格的油田(连通分量)数量 // 嵌套循环遍历二维网格的每一个格子 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 如果发现一块属于新油田的 pocket if (grid[i][j] == '@') { oil_fields++; // 油田数量加 1 dfs(i, j, m, n, grid); // 调用 DFS 淹没与其相邻的所有油田格子 } } } cout << oil_fields << endl; // 输出该组数据的计算结果 }
解析:此部分是二维网格连通块求解的核心控制区。两层 for 循环覆盖所有坐标。一旦触发 grid[i][j] == '@',说明找到了新油田的某一位置。通过 DFS 蔓延清除所有相连的 @ 后,再继续寻找下一块完全独立的油田。