1 条题解

  • 0
    @ 2025-11-11 8:45:48

    题目理解

    我们有一个有向图,包含 NN 个城市和 MM 条有向道路,每条道路有长度 LiL_i 和关闭费用 CiC_i
    给定起点 AA 和终点 BB,国王希望关闭所有出现在AABB 且总长度不超过 DD 的路径上的道路。
    对于多个询问 D1,D2,,DQD_1, D_2, \dots, D_Q,需要分别计算关闭这些道路的最小总费用。


    问题转化

    设一条路径 PP 包含若干条边 e1,e2,,eke_1, e_2, \dots, e_k,路径总长度 len(P)=Leilen(P) = \sum L_{e_i}
    对于给定的 DD,需要关闭所有满足以下条件的边 ee
    存在一条从 AABB 的路径 PP 包含 ee,且 len(P)Dlen(P) \le D


    关键观察

    对于一条边 (u,v,L,C)(u, v, L, C),它出现在某条从 AABB 且长度不超过 DD 的路径上,当且仅当: [ dist(A, u) + L + dist(v, B) \le D ] 其中 dist(A,u)dist(A, u) 表示从 AAuu 的最短距离,dist(v,B)dist(v, B) 表示从 vvBB 的最短距离。


    算法步骤

    1. 计算最短路径

    • AA 出发,在原图上跑 Dijkstra,得到 distA[u]dist_A[u](从 AAuu 的最短距离)。
    • BB 出发,在反图上跑 Dijkstra,得到 distB[v]dist_B[v](从 vvBB 的最短距离)。

    2. 计算每条边的关键值

    对于边 i=(u,v,L,C)i = (u, v, L, C),计算: [ key_i = dist_A[u] + L + dist_B[v] ] 这表示经过该边的最短可能路径长度。

    3. 预处理前缀和

    • 将所有边按 keyikey_i 从小到大排序。
    • 计算关闭费用的前缀和数组 prefix[k]=j=1kCijprefix[k] = \sum_{j=1}^k C_{i_j}

    4. 回答询问

    对于每个询问 DD,在排序后的边数组中二分查找最大的 kk 使得 keyikDkey_{i_k} \le D,答案即为 prefix[k]prefix[k]


    代码实现解析

    1. 数据结构

    struct node {
        long long s, e, z, t;  // 起点, 终点, 长度, 关闭费
    } ak[200010];
    struct abo {
        long long g, h;  // key值, 关闭费
    } jg[200010];
    struct nd {
        vector<long long> w, nxt;  // 邻接表存储
        long long dist;
    } g[200010];
    

    2. Dijkstra 算法

    void dijkstra(int s) {
        // 初始化距离为无穷大
        for (int i = 1; i <= n; ++i)
            g[i].dist = 4e9;
        
        g[s].dist = 0;
        priority_queue<pq, vector<pq>, greater<pq>> q;
        q.push({s, 0});
        
        while (!q.empty()) {
            // 取出当前距离最小的节点
            pq c = q.top();
            q.pop();
            
            if (c.dist != g[c.x].dist) continue;
            
            // 松弛相邻节点
            for (int i = 0; i < g[c.x].nxt.size(); i++) {
                int neighbor = g[c.x].nxt[i];
                long long new_dist = c.dist + g[c.x].w[i];
                if (new_dist < g[neighbor].dist) {
                    g[neighbor].dist = new_dist;
                    q.push({neighbor, new_dist});
                }
            }
        }
    }
    

    3. 主流程

    int main() {
        // 读入数据
        scanf("%lld%lld%lld%lld", &n, &m, &a, &b);
        
        // 建原图
        for (long long i = 1; i <= m; i++) {
            scanf("%lld%lld%lld%lld", &l1, &l2, &l3, &l4);
            g[l1].nxt.push_back(l2);
            g[l1].w.push_back(l3);
            ak[i] = {l1, l2, l3, l4};
        }
        
        // 计算从A出发的最短距离
        dijkstra(a);
        for (int i = 1; i <= n; i++) 
            mn[i] = g[i].dist;
        
        // 清空图,建反图
        for (int i = 1; i <= n; i++) {
            g[i].w.clear();
            g[i].nxt.clear();
        }
        for (int i = 1; i <= m; i++) {
            g[ak[i].e].nxt.push_back(ak[i].s);
            g[ak[i].e].w.push_back(ak[i].z);
        }
        
        // 计算到B的最短距离(在反图上)
        dijkstra(b);
        
        // 计算每条边的key值
        for (long long i = 1; i <= m; i++)
            jg[i] = {mn[ak[i].s] + ak[i].z + g[ak[i].e].dist, ak[i].t};
        
        // 排序并计算前缀和
        sort(jg + 1, jg + m + 1, cmp);
        for (long long i = 1; i <= m; i++)
            ans[i] = ans[i - 1] + jg[i].h;
        
        // 处理询问
        scanf("%lld", &qq);
        for (long long i = 1; i <= qq; i++) {
            scanf("%lld", &l1);
            // 二分查找
            long long l = 0, r = m;
            while (l < r) {
                long long mid = (l + r + 1) / 2;
                if (jg[mid].g > l1) r = mid - 1;
                else l = mid;
            }
            cout << ans[l] << endl;
        }
        
        return 0;
    }
    

    复杂度分析

    • 时间复杂度

      • 两次 Dijkstra:O((N+M)logN)O((N+M)\log N)
      • 排序:O(MlogM)O(M\log M)
      • 每次询问二分:O(logM)O(\log M)
      • 总复杂度:O((N+M)logN+MlogM+QlogM)O((N+M)\log N + M\log M + Q\log M)
    • 空间复杂度O(N+M)O(N+M)


    算法标签

    • 图论
    • 最短路径
    • 二分查找
    • 前缀和

    总结

    本题通过最短路径预处理二分查找高效解决了路径相关的关闭费用计算问题。
    核心思路是将"边是否在满足条件的路径上"转化为对边的一个关键值的比较,通过排序和前缀和优化询问回答。
    该解法充分利用了最短路径的性质,将复杂的最短路径包含判断转化为简单的数值比较。

    • 1

    信息

    ID
    5191
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者