题解:洛谷 P1394 山上的国度
【题目来源】
【题目描述】
有一个神秘的小国坐落在南方的青山之上,只有当黄昏时,落日耀眼的余晖刺破薄雾的遮拦,有机缘者才可看到小山上面的 \(n\) 个美丽的村庄。
传说这个古老的国家里有 \(m\) 条枢纽管道,每一条苍老的管道连接着两个村庄,千百年来为村民提供水源的流通。
\(n\) 个村庄里只有一个水库,从有水库的村庄通过这些枢纽管道向其它村庄提供水源。大家都明白水往低处流,所有村庄都能得到水库的供水。
黄小明就是那个有机缘者,同时他也是个偏执狂(把小猫绑在一起的那个变态小明),他迫切的想要知道水库应该在哪一个村庄,你能帮他解决疑惑吗?
【输入】
第一行输入 \(n,m\)(\(n,m \leq 300\))。
第二行输入 \(n\) 个正整数,第 \(i\) 个数表示第 \(i\) 个村庄的海拔。之后 \(m\) 行每行两个数表示这两个村庄之间有一条道路。(同海拔之间不能相互流水)
【输出】
若存在这样的村庄,输出两行:第一行为 Oui, j'ai trouve la solution.,第二行为村庄的编号。
否则,请输出 Non。
【输入样例】
4 2
1 2 3 4
1 2
3 4
【输出样例】
Non
【核心思想】
-
问题分析:给定 \(n\) 个村庄的海拔 \(a_i\) 和 \(m\) 条无向管道,水只能从海拔高的村庄流向海拔低的村庄(同海拔不能流)。需要找到一个村庄作为水库,使得水能够通过管道网络到达所有其他村庄。这是一个DFS-图问题,关键在于枚举每个村庄作为起点,通过有向约束的 DFS 判断能否覆盖全图。
-
算法选择:
- 枚举 + DFS:枚举每个村庄 \(i\) 作为水库起点,执行深度优先搜索
- 有向化遍历:在 DFS 中增加海拔约束,将无向图按海拔高低转化为有向图进行遍历
-
关键步骤:
- 初始化:读取 \(n\)(村庄数)、\(m\)(管道数)、\(a[1..n]\)(海拔数组),用链式前向星建无向图
- 枚举水库位置(遍历 \(i\) 从 \(1\) 到 \(n\)):
- 清空访问标记数组 \(st[1..n]\)
- 从 \(i\) 出发 DFS:\(dfs(i, 0)\)
- 标记 \(st[u] = true\)
- 遍历 \(u\) 的所有邻接点 \(v\):
- 跳过父节点 \(fa\)(避免来回走)
- 跳过已访问的 \(v\)
- 关键约束:若 \(a[u] \leq a[v]\) 则跳过(水往低处流,必须严格从高到低)
- 递归 \(dfs(v, u)\)
- 判定覆盖:检查是否所有 \(st[j] = true\)(\(j = 1..n\))
- 若全部可达:输出法语提示和村庄编号 \(i\),程序结束
- 无解:若枚举完所有村庄均不满足,输出
Non
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times (n + m))\),枚举 \(n\) 个起点,每次 DFS 遍历 \(O(n + m)\)
- 空间复杂度:\(O(n + m)\),链式前向星存图 + 访问标记数组
-
DFS-图的核心思想:
- 有向化无向图:通过海拔约束 \(a[u] > a[v]\) 在遍历过程中动态决定边的方向,无需显式建 DAG
- 枚举验证:由于不知道哪个村庄是最高点,枚举每个节点作为起点,用 DFS 验证其可达集合
- 父节点剪枝:通过 \(fa\) 参数避免在无向图中来回遍历,防止死循环
- 贪心最高点:水库必须是全局最高点或局部最高点,因为只有从高到低才能覆盖全图
- 适用于有向/条件图遍历、连通性判定、枚举起点类问题
【算法标签】
普及 #DFS-图
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 305, M = N * N; // N: 最大村庄数, M: 最大边数(无向图双向边)
int n, m; // n: 村庄数量, m: 管道数量
int a[N]; // a[i]: 第i个村庄的海拔高度
bool st[N]; // st[i]: 标记村庄i是否被访问过(能否从水库到达)
int h[N], e[M], ne[M], idx; // 链式前向星存图
// 链式前向星:添加无向边
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// DFS:从水库u出发,判断水能否流向其他村庄
// 水只能从海拔高的村庄流向海拔低的村庄(同海拔不能流)
void dfs(int u, int fa)
{
st[u] = true; // 标记当前村庄已被访问(能到达)
// 遍历u的所有邻接村庄
for (int i = h[u]; i != -1; i = ne[i])
{
int v = e[i]; // 邻接村庄v
if (v == fa) continue; // 跳过父节点,避免来回走
if (st[v]) continue; // 已访问过,跳过
if (a[u] <= a[v]) continue; // 水往低处流:u的海拔必须严格大于v才能流向v
dfs(v, u); // 递归DFS:水从u流向v
}
}
int main()
{
cin >> n >> m; // 读入村庄数和管道数
// 读入每个村庄的海拔高度
for (int i = 1; i <= n; i++)
cin >> a[i];
// 初始化链式前向星
memset(h, -1, sizeof(h));
// 读入m条管道,建立无向图
while (m--)
{
int u, v;
cin >> u >> v;
add(u, v), add(v, u); // 双向建边
}
// 枚举每个村庄作为水库的位置
for (int i = 1; i <= n; i++)
{
memset(st, 0, sizeof(st)); // 清空访问标记
dfs(i, 0); // 从村庄i出发DFS,看水能流向哪些村庄
// 检查是否所有村庄都能被水到达
bool flag = true;
for (int j = 1; j <= n; j++)
{
if (st[j] == 0) // 存在村庄j无法到达
{
flag = false; // 村庄i不能作为水库
break;
}
}
// 若所有村庄都能到达,则村庄i可以作为水库
if (flag)
{
cout << "Oui, j'ai trouve la solution." << endl;
cout << i << endl;
return 0; // 找到解,直接退出
}
}
// 没有任何村庄可以作为水库
cout << "Non" << endl;
return 0;
}
【运行结果】
4 2
1 2 3 4
1 2
3 4
Non
浙公网安备 33010602011771号