带依赖关系的最短路问题

大观园里忽起谣言"林妹妹要回南方了!"宝玉听后顿时失魂,目直口呆,如堕迷津。小码妹忙扶他入怡红院,却见院中石径化作一座无形迷宫:共nn个点,编号1∼n1∼n,11号点即宝玉卧榻,nn号点为林妹妹住处。

这些点通过mm条回廊相连,每条回廊都有相应的脚程耗费(可看作边权)。这nn个点中,有kk个特殊点,这些特殊点之间会产生通行规矩,通行规矩形如:x y,表示必须要先访问过编号为xx的点后才能访问编号为yy的点。

现在给到tt条通行规矩,问:在满足所有通行规矩的前提下,宝玉去到林妹妹住处所走路径的总脚程耗费最小是多少,即从编号为11的点走到编号为nn的点最小边权和是多少?

格式

输入格式:

第一行一个整数T(1≤T≤2×1e5),表示测试数据组数。

对于每组测试数据:

第一行四个整数n,m,k,t(1≤n,m≤2×105,0≤k≤5,0≤t≤k2)。

接下来m行,每行三个整数u,v,w(1≤u,v≤n,1≤w≤104),表示点u和点v之间有一条边权为w的边。

接下来一行k个互不相同的整数d1​∼dk​(2≤di​≤n),表示关键点序列(若k=0,则该行不存在,并不保留空行)。

接下来t行,每行两个整数x,y(x,y∈d),表示依赖关系。

保证所有组的n之和不超过2×1e5,m之和不超过2×1e5。

输出格式:

对于每组测试数据:

若能从1号点走到n号点,输出一行一个整数,表示最小边权和,否则输出一行impossible

这道题的核心是一个带有依赖限制的最短路问题

解题思路

  1. 限制条件分析

    题目要求"必须先访问过 x 才能访问y"。由于特殊点 k 的数量非常小,这暗示我们可以将"已经访问过哪些特殊点"作为一个状态。

  2. 状态设计

    我们可以使用状压 Dijkstra(Bitmask Dijkstra)。
    dist[mask][u] 表示从起点1 出发,到达点 u,且当前已经访问过的特殊点集合为 mask(一个二进制数,第 i 位为 1 表示第 i 个特殊点已访问)时的最小路径长度。

  3. 转移规则

    在从点 u 移动到点 v 时:

    • 如果 v 不是 特殊点:直接转移,mask 不变。

    • 如果 v 特殊点:

      • 如果 v 已经在 mask 中:说明之前访问过,直接转移,mask 不变。

      • 如果 v 不在 mask 中:

        • 检查 v 的所有依赖(即必须先访问的点)。如果这些依赖点都已经包含在 mask 中,则允许进入 v,并将 v 加入 mask

        • 如果有任何一个依赖点不在 mask 中,则禁止走这条路(无法踏入 v)。

  4. 终点处理

    最终答案为所有可能 maskdist[mask][n] 的最小值。

#include <iostream>

#include <vector>

#include <queue>

#include <algorithm>

using namespace std;

// 使用 long long 防止路径权值和溢出

typedef long long ll;

const ll INF = 1e16;

struct State {

int u;

int mask;

ll d;

// 优先队列小顶堆,按距离排序

bool operator>(const State& other) const {

return d > other.d;

}

};

void solve() {

int n, m, k, t;

if (!(cin >> n >> m >> k >> t)) return;

// 邻接表存储图

vector<vector<pair<int, int>>> adj(n + 1);

for (int i = 0; i < m; ++i) {

int u, v, w;

cin >> u >> v >> w;

adj[u].push_back({v, w});

adj[v].push_back({u, w});

}

// 映射特殊点到 0 ~ k-1 的索引

vector<int> special_idx(n + 1, -1);

if (k > 0) {

for (int i = 0; i < k; ++i) {

int node;

cin >> node;

special_idx[node] = i;

}

}

// 存储每个特殊点的依赖规则(位掩码形式)

vector<int> prereq(k, 0);

for (int i = 0; i < t; ++i) {

int x, y;

cin >> x >> y;

int ix = special_idx[x];

int iy = special_idx[y];

if (ix != -1 && iy != -1) {

prereq[iy] |= (1 << ix); // 访问 iy 前必须先拥有 mask 中的 ix 位

}

}

// Dijkstra 状态数组:dist[mask][node]

int num_masks = (1 << k);

vector<vector<ll>> dist(num_masks, vector<ll>(n + 1, INF));

priority_queue<State, vector<State>, greater<State>> pq;

// 初始化起点:根据题目 1 号点不是特殊点,初始化 mask 为 0

dist[0][1] = 0;

pq.push({1, 0, 0});

while (!pq.empty()) {

State curr = pq.top();

pq.pop();

if (curr.d > dist[curr.mask][curr.u]) continue;

for (auto& edge : adj[curr.u]) {

int v = edge.first;

int w = edge.second;

int nxt_m = curr.mask;

// 如果下一个点是特殊点

if (special_idx[v] != -1) {

int idx = special_idx[v];

// 如果之前没访问过该点,需要检查依赖

if (!((curr.mask >> idx) & 1)) {

// 检查当前已访问集合是否包含了该点的所有前置要求

if ((curr.mask & prereq[idx]) == prereq[idx]) {

nxt_m |= (1 << idx); // 标记该特殊点已访问

} else {

// 依赖未满足,无法通行

continue;

}

}

}

// 更新最短路径并入队

if (dist[nxt_m][v] > curr.d + w) {

dist[nxt_m][v] = curr.d + w;

pq.push({v, nxt_m, dist[nxt_m][v]});

}

}

}

// 统计到达 n 点的所有状态中的最小值

ll result = INF;

for (int i = 0; i < num_masks; ++i) {

result = min(result, dist[i][n]);

}

if (result >= INF) cout << "impossible" << "\n";

else cout << result << "\n";

}

int main() {

// 优化输入输出效率

ios::sync_with_stdio(false);

cin.tie(NULL);

int T;

if (!(cin >> T)) return 0;

while (T--) {

solve();

}

return 0;

}

在普通 Dijkstra 中,你的状态只有一个维度:你在哪(点 u)?

但在本题中,你在哪并不决定你能不能去下一站,还取决于你手里拿到了几把"钥匙"。

所以,我们要给状态"升维"。现在的状态是一个二元组:(u, mask)。

分层图最短路也是这个道理,将状态升维了

相关推荐
洛水水1 小时前
【力扣100题】22. 矩阵置零
算法·leetcode·矩阵
Liangwei Lin1 小时前
LeetCode 78. 子集
数据结构·算法·leetcode
多加点辣也没关系1 小时前
数据结构与算法|第二十四章:算法思维总结与实战
算法·代理模式
炽烈小老头2 小时前
【每天学习一点算法 2026/05/11】排序链表
学习·算法·链表
wefg12 小时前
一些零散的算法
c++·算法
khalil10202 小时前
代码随想录算法训练营Day-48 单调栈02 | 42. 接雨水、84.柱状图中最大的矩形
数据结构·c++·算法·leetcode·单调栈·接雨水
Hcoco_me2 小时前
Ai:Agent/ infra / 智驾 / 推广算法 题库
人工智能·深度学习·算法·自动驾驶·剪枝
项目申报小狂人2 小时前
提出了一种带双向搜索的粒子群优化算法,一种基于双四元数运动优化的新型无人机3D路径规划方法及应用
算法·3d·无人机
驼同学.2 小时前
牛客网面试TOP101 - Python算法学习指南
python·算法·面试