题解:洛谷 P1394 山上的国度

【题目来源】

洛谷: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

【核心思想】

  1. 问题分析:给定 \(n\) 个村庄的海拔 \(a_i\)\(m\) 条无向管道,水只能从海拔高的村庄流向海拔低的村庄(同海拔不能流)。需要找到一个村庄作为水库,使得水能够通过管道网络到达所有其他村庄。这是一个DFS-图问题,关键在于枚举每个村庄作为起点,通过有向约束的 DFS 判断能否覆盖全图。

  2. 算法选择

    • 枚举 + DFS:枚举每个村庄 \(i\) 作为水库起点,执行深度优先搜索
    • 有向化遍历:在 DFS 中增加海拔约束,将无向图按海拔高低转化为有向图进行遍历
  3. 关键步骤

    • 初始化:读取 \(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
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \times (n + m))\),枚举 \(n\) 个起点,每次 DFS 遍历 \(O(n + m)\)
    • 空间复杂度:\(O(n + m)\),链式前向星存图 + 访问标记数组
  5. 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
posted @ 2026-08-10 12:25  团爸讲算法  阅读(10)  评论(0)    收藏  举报