题解:洛谷 P14923 猫和老鼠

【题目来源】

洛谷:P14923 [GESP202512 八级] 猫和老鼠 - 洛谷

【题目描述】

猫和老鼠所在的庄园可以视为一张由 \(n\) 个点和 \(m\) 条带权无向边构成的连通图。结点依次以 \(1,2,\ldots,n\) 编号,结点 \(i\)\(1\le i\le n\))有价值为 \(c_i\) 的奶酪。在 \(m\) 条带权无向边中,第 \(i\)\(1\le i\le m\))条无向边连接结点 \(u_i\) 与结点 \(v_i\),边权 \(w_i\) 表示猫和老鼠通过这条边所需的时间。

猫窝位于结点 \(a\),老鼠洞位于结点 \(b\)。对于老鼠而言,结点 \(u\)安全的当且仅当:

  • 老鼠能规划一条从结点 \(u\) 出发逃往老鼠洞的路径,使得对于路径上任意结点 \(x\)(包括结点 \(u\) 与老鼠洞)都有:猫从猫窝出发到结点 \(x\) 的最短时间严格大于老鼠从结点 \(u\) 沿这条路径前往结点 \(x\) 所需的时间。

老鼠在拿取安全结点的奶酪时不存在被猫抓住的可能,但在拿取不是安全结点的奶酪时则不一定。为了确保万无一失,老鼠决定只拿取安全结点放置的奶酪。请你计算老鼠所能拿到的奶酪价值之和。

【输入】

第一行,两个正整数 \(n,m\),分别表示图的结点数与边数。

第二行,两个正整数 \(a,b\),分别表示猫窝的结点编号,以及老鼠洞的结点编号。

第三行,\(n\) 个正整数 \(c_1,c_2,\ldots,c_n\),表示各个结点的奶酪价值。

接下来 \(m\) 行中的第 \(i\) 行(\(1\le i\le m\))包含三个正整数 \(u_i,v_i,w_i\),表示图中连接结点 \(u_i\) 与结点 \(v_i\) 的边,边权为 \(w_i\)

【输出】

输出一行,一个整数,表示老鼠所能拿到的奶酪价值之和。

【输入样例】

5 5
1 2
1 2 4 8 16
1 2 4
2 3 3
3 4 1
2 5 2
3 1 8

【输出样例】

22

【核心思想】

  1. 问题分析:给定一张 \(n\) 个点、\(m\) 条带权无向边的连通图,每个点有价值 \(c_i\)。猫窝在 \(a\),老鼠洞在 \(b\)。对于老鼠,结点 \(u\) 是安全的当且仅当:存在一条从 \(u\)\(b\) 的路径,使得对于路径上任意结点 \(x\)(包括 \(u\)\(b\)),猫从 \(a\)\(x\) 的最短时间严格大于老鼠从 \(u\) 沿该路径到 \(x\) 所需的时间。求所有安全结点的价值之和。本质是在图上寻找满足特定时间约束的可行路径,可转化为最短路径与动态规划问题。

  2. 算法选择

    • Dijkstra 最短路:分别以猫窝 \(a\) 和老鼠洞 \(b\) 为源点,计算到所有点的最短距离 \(dist_a[x]\)\(dist_b[x]\)
    • 最短路径 DAG 上的 DP:定义 \(T[x] = dist_a[x] + dist_b[x]\)。安全条件等价于:对于从 \(u\)\(b\) 的最短路径上的每个点 \(x\),有 \(dist_b[u] < T[x]\)。即 \(dist_b[u] < \min_{x \in \text{路径}} T[x]\)。为了判断 \(u\) 是否安全,需要计算从 \(b\)\(u\) 的所有最短路径中,路径上 \(T\) 的最小值的最大值,记为 \(max\_minT[u]\)。若 \(dist_b[u] < max\_minT[u]\),则 \(u\) 安全。
  3. 关键步骤

    • 第一次 Dijkstra:从 \(a\) 出发,计算 \(dist_a[1..n]\)
    • 第二次 Dijkstra:从 \(b\) 出发,计算 \(dist_b[1..n]\)
    • 计算 \(T\)\(T[i] = dist_a[i] + dist_b[i]\)
    • 最短路径 DAG 上的 DP
      • 初始化 \(max\_minT[b] = T[b]\),其余为 \(0\)
      • 将所有点按 \(dist_b\) 升序排序。
      • 遍历每个点 \(u\),对于每条边 \((u, v, w)\),若 \(dist_b[u] + w == dist_b[v]\),则 \(v\) 在从 \(b\) 出发的最短路径上,更新 \(max\_minT[v] = \max(max\_minT[v], \min(max\_minT[u], T[v]))\)
    • 统计答案:遍历所有点 \(u\),若 \(dist_b[u] < max\_minT[u]\),则 \(u\) 安全,累加 \(c[u]\)
    • 输出:总价值。
  4. 时间/空间复杂度

    • 时间复杂度:两次 Dijkstra 为 \(O((n+m)\log n)\),DAG 上 DP 为 \(O(n+m)\),总复杂度 \(O((n+m)\log n)\)
    • 空间复杂度:\(O(n+m)\),存储图、距离数组和 DP 数组。
  5. Dijkstra + 最短路径 DP 的核心思想

    • 安全条件转化:将“猫到 \(x\) 的时间 > 老鼠到 \(x\) 的时间”转化为 \(dist_b[u] < dist_a[x] + dist_b[x]\),即 \(dist_b[u] < T[x]\)。因此 \(u\) 安全当且仅当从 \(u\)\(b\) 的最短路径上所有点的 \(T\) 值都大于 \(dist_b[u]\)
    • 最短路径 DAG:在从 \(b\) 出发的最短路径构成的 DAG 上,沿最短路径方向传播 \(T\) 的最小值,计算每个点可达的最优(最大)\(minT\)
    • 适用场景:适用于图上带有时间窗或距离约束的路径可行性问题,通过最短路和 DAG 上的动态规划高效求解。

【算法标签】

普及+ #Dijkstra

【代码详解】

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 100005;
int n, m, a, b;  // n: 节点数, m: 边数, a: 节点A, b: 源点
int c[N];  // 每个节点的权值
int ans;  // 最终答案
int dist[N];  // 从源点b到各节点的最短距离
struct Node
{
    int v, w;  // v: 目标节点, w: 距离/边权
};
vector<Node> g[N];  // 邻接表存储图
priority_queue<Node, vector<Node> > pq;  // 优先队列,用于Dijkstra
bool operator<(Node x, Node y)  // 重载<运算符,使优先队列成为小顶堆
{
    return x.w > y.w;  // 注意:priority_queue默认是大顶堆,这里用>实现小顶堆
}
bool st[N];  // Dijkstra访问标记,标记节点是否已确定最短路径

// Dijkstra算法,计算从节点b到所有节点的最短距离
void dijkstra()
{
    memset(dist, 0x3f, sizeof(dist));  // 初始化所有距离为无穷大
    pq.push({b, 0});  // 从b点开始,距离为0
    dist[b] = 0;  // 源点b到自己的距离为0

    while (!pq.empty())
    {
        int u = pq.top().v;  // 当前节点
        int w = pq.top().w;  // 当前距离
        pq.pop();

        if (st[u])  // 如果已确定最短路径,跳过
        {
            continue;
        }
        st[u] = true;  // 标记为已确定
        dist[u] = w;  // 记录最短距离(这里w已经是dist[u],但赋值确保一致性)

        // 遍历当前节点的所有邻接边
        for (int i = 0; i < g[u].size(); i++)
        {
            int v = g[u][i].v;  // 邻接节点
            int weight = g[u][i].w;  // 边权

            // 松弛操作:如果通过u到达v更短,则更新
            if (dist[v] > dist[u] + weight)
            {
                dist[v] = dist[u] + weight;  // 更新最短距离
                pq.push({v, dist[v]});  // 入队
            }
        }
    }
}

signed main()
{
    cin >> n >> m >> a >> b;  // 输入节点数、边数、两个特殊节点

    // 输入每个节点的权值
    for (int i = 1; i <= n; i++)
    {
        cin >> c[i];
    }

    // 输入图的边
    while (m--)
    {
        int u, v, w;
        cin >> u >> v >> w;
        g[u].push_back({v, w});  // 无向图
        g[v].push_back({u, w});  // 无向图
    }

    dijkstra();  // 运行Dijkstra算法,计算从b到所有节点的最短距离

    // 统计所有到b的距离小于到a距离的节点的权值和
    for (int i = 1; i <= n; i++)
    {
        if (dist[i] < dist[a])  // 如果节点i到b的距离小于a到b的距离
        {
            ans += c[i];  // 累加节点i的权值
        }
    }

    cout << ans << endl;  // 输出结果

    return 0;  // 程序正常结束
}

【运行结果】

5 5
1 2
1 2 4 8 16
1 2 4
2 3 3
3 4 1
2 5 2
3 1 8
22
posted @ 2026-09-13 18:26  团爸讲算法  阅读(3)  评论(0)    收藏  举报