双关键字dij
Dijkstra 进阶:双关键字与多维权值最短路
在常规的最短路问题中,我们只需要维护一个 dis 数组。但当题目出现多个维度的代价(如距离、花费、心情、特定物品数量)时,我们需要在优先队列和松弛操作中引入多关键字比较逻辑。
1. 基础多关键字最短路:逃离石楠花大学
来源文件:T_594366_逃离石楠花大学.cpp
题面抽象
给定一张有向图,图中的边有距离权值 $w$,部分节点上有特定物品(石楠花)。要求从起点 $s$ 到终点 $t$ 寻找一条最优路径,评判标准如下:
- 第一优先级:经过含有该物品的节点数量(
cnt)最少。 - 第二优先级:在物品数量相同的情况下,路径总距离(
w)最短。
核心思路
将单点状态由一维扩展为二维。在标准的 Dijkstra 框架下,维护两个状态数组:val(记录物品数量)和 dis(记录距离)。
- 状态重载:在优先队列的
Node结构体中,重载<运算符以体现优先级。因为 C++ 的priority_queue默认是大根堆,为了实现小根堆的效果,当cnt != v.cnt时返回cnt > v.cnt,否则返回w > v.w。 - 二维松弛操作:
- 绝对优势:如果新路径的物品数量严格更少(
now_cnt < val[v]),则无条件覆盖现有的val[v]和dis[v],并入队。 - 次级优势:如果物品数量持平(
now_cnt == val[v]),则比较距离。若新路径距离更短(now_dist < dis[v]),则更新dis[v]并入队。
点击查看代码
int a[N];
vector<pair<int, int>> g[N];
int n, m, k, s, t;
struct Node {
int x, w, cnt;
bool operator<(const Node &v) const {
if (cnt != v.cnt)
return cnt > v.cnt; // 第一关键字:优先保证花最少
else
return w > v.w; // 第二关键字:花一样多的情况下,距离最短
}
};
void dij() {
priority_queue<Node> q;
vector<int> dis(n + 1, 1e18);
vector<int> val(n + 1, 1e18);
vector<bool> vis(n + 1, 0);
q.push({s, 0, a[s]});
val[s] = a[s];
dis[s] = 0;
while (q.size()) {
auto [u, w, cnt] = q.top();
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto [v, w] : g[u]) {
if (vis[v]) continue;
int now_cnt = cnt + a[v];
int now_dist = dis[u] + w;
if (now_cnt < val[v]) {
dis[v] = now_dist;
val[v] = now_cnt;
q.push({v, dis[v], val[v]});
} else if (now_cnt == val[v]) {
if (dis[v] > now_dist) {
dis[v] = now_dist;
val[v] = now_cnt;
q.push({v, dis[v], val[v]});
}
}
}
}
if (dis[t] == 1e18)
cout << -1 << endl;
else
cout << dis[t] << endl;
}
void solve() {
cin >> n >> m >> k >> s >> t;
for (int i = 1; i <= k; i++) {
int t;
cin >> t;
a[t] = 1;
}
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
}
dij();
}
2. 约束截断与最值极化:天梯赛 2025 L3-1
来源文件:天梯赛2025L3 - 1.cpp
题面抽象
给定一张无向图,每条道路除了花费 $w$ 之外,还有一个“心情指数” $cnt$。同时给定了预算上限 $b$。多次查询不同的起点 $u$,要求输出:
- 在预算 $b$ 范围内,走最便宜的路线能够到达的所有城市。
- 在上述可达的城市中,挑选出沿途心情指数总和最高的城市列表。
核心思路
此题在双权值的基础上增加了全局约束截断(剪枝)和多目标最值查询。其路径评判标准为:第一优先级是花费 $w$ 最小,第二优先级是心情指数 $cnt$ 最大。
-
带方向的状态重载:在优先队列中,花费要求越小越好(
w > v.w),而心情指数要求越大越好(cnt < v.cnt)。这种一正一反的重载是处理收益与代价混合问题的标准写法。 -
全局阈值剪枝:在从优先队列取出元素以及进行松弛操作扩展新节点时,如果发现
now_dis > b,则直接continue截断,避免扩展无效的超预算状态。 -
严格的松弛逻辑:
-
当
dis[v] > now_dis时,说明找到了更便宜的路径,无条件更新dis[v]和mood[v]。 -
当
dis[v] == now_dis时,必须且仅当now_cnt > mood[v]时,才去更新心情指数并重新入队。 -
后处理统计:跑完单源最短路后,通过遍历所有非起点的节点 $i$,筛选出满足
dis[i] <= b的节点输出可达集合,同时维护一个mx = max(mx, mood[i])用于第二轮输出心情最高的城市集合。
点击查看代码
const int N = 505;
int a[N];
int n, m, b, k;
struct Node {
int x, w, cnt;
bool operator<(const Node &v) const {
if (w != v.w) return w > v.w;
return cnt < v.cnt;
}
};
vector<Node> g[N];
int dis[N];
int mood[N];
bool vis[N];
void dij(int st) {
priority_queue<Node> q;
q.push({st, 0, 0});
// vector<int> dis(n + 1, 1e9);
// vector<int> mood(n + 1, 0);
// vector<bool> vis(n + 1);
for (int i = 1; i <= n; i++) {
dis[i] = 1e9;
mood[i] = 0;
vis[i] = false;
}
dis[st] = 0;
while (q.size()) {
auto [u, w, cnt] = q.top();
q.pop();
if (vis[u]) continue;
if (w > b) continue;
vis[u] = 1;
for (auto [v, w, cc] : g[u]) {
if (vis[v]) continue;
int now_dis = dis[u] + w;
if (now_dis > b) continue;
int now_cnt = mood[u] + cc;
if (dis[v] > now_dis) {
dis[v] = now_dis;
mood[v] = now_cnt;
q.push({v, now_dis, now_cnt});
} else if (dis[v] == now_dis && now_cnt > mood[v]) {
dis[v] = now_dis;
mood[v] = now_cnt;
q.push({v, now_dis, now_cnt});
}
}
}
bool f = 0;
int mx = 0;
for (int i = 1; i <= n; i++) {
if (i != st && dis[i] <= b) {
mx = max(mx, mood[i]);
if (f) cout << ' ';
cout << i;
f = 1;
}
}
if (f == 0) {
cout << "T_T" << endl;
return;
}
cout << endl;
f = 0;
for (int i = 1; i <= n; ++i) {
if (i != st && dis[i] <= b && mood[i] == mx) {
if (f == 1) cout << ' ';
cout << i;
f = 1;
}
}
cout << endl;
}
void solve() {
cin >> b >> n >> m >> k;
for (int i = 1; i <= m; i++) {
int u, v, w, cnt;
cin >> u >> v >> w >> cnt;
g[u].push_back({v, w, cnt});
g[v].push_back({u, w, cnt});
}
while (k--) {
int u;
cin >> u;
dij(u);
}
}
💡 核心总结与避坑指南
- 重载运算符的符号一致性:在双关键字 Dijkstra 中,
operator<的书写极易出错。务必明确题目对第二关键字是求最大还是最小。求最小用>,求最大用<。 - 入队条件:只有在发生实质性更新(无论是第一关键字还是第二关键字变优)时,才将新状态
push进优先队列,否则会导致队列膨胀从而 TLE。

浙公网安备 33010602011771号