最短路+次短路

最短路算法

  • 提供题单: 点我!!!
  • 最短路分单源最短路(Bellman-Ford 和 dijkstra)和多源最短路(Floyd)

贝尔曼福特(Bellman-Ford)算法

  • 可以证明,对于一个没有负环的图(边权可以为负),\(ans\) 最多更新 \(n - 1\)
    • 可以看如果总共走了 \(n\) 条边,那么就说明有点重复走了,那么会延长路径
  • 显然,\(O(nm)\)
模板 Code
void Bellman_Ford(){
  fill(ans + 1, ans + 1 + n, 1e18);
  ans[1] = 0;
  for(int i = 1; i < n; i++){     // 类似广搜
     for(int j = 1; j <= m; j++){ // 进行转移
         ans[q[j].v] = min(ans[q[j].v], ans[q[j].u] + q[j].w);
     }
  }
}
  • Bellman-Ford 可以用来判断是否有负环,负环是可以一直被更新最小值的
判负环 Code
void Bellman_Ford(){
  fill(ans, ans + 1 + n, Inf);
  ans[1] = 0;
  for(int i = 1; i <= n; i++){
    for(int j = 1; j <= m; j++){
      if(ans[eg[j].x] < Inf) ans[eg[j].y] = min(ans[eg[j].y], ans[eg[j].x] + eg[j].w); // 这个 if 语句是细节
    }
  }
  for(int j = 1; j <= m; j++){
    if(ans[eg[j].x] < Inf && ans[eg[j].x] + eg[j].w < ans[eg[j].y]){ // 如果答案还可以被更行,就说明有负环
      cout << "有负环\n";
      return;
    }
  }
  cout << "没有负环\n";
}
求哪些点在负环上 Code
void Bellman_Ford(){
  fill(ans, ans + 1 + n, Inf);
  ans[1] = 0;
  for(int i = 1; i <= n; i++){
    for(int j = 1; j <= m; j++){
      if(ans[eg[j].x] < Inf) ans[eg[j].y] = min(ans[eg[j].y], ans[eg[j].x] + eg[j].w);
    }
  }
  for(int i = 1; i <= n; i++){ // 因为有的点需要进行两轮才能判断到,所以再进行一轮
    for(int j = 1; j <= m; j++){
      if(ans[eg[j].x] < Inf && (vis[eg[j].x] || ans[eg[j].x] + eg[j].w < ans[eg[j].y])){ // 如果一个点可以从一个负环上的点转移来,那么这一个点一定是负环上的点
        vis[eg[j].y] = 1;
      }
    }
  }
  for(int i = 1; i <= n; i++){
    if(vis[i]) // 点 i 在负环上
  }
}

迪杰斯特拉(dijkstra)算法

  • 单元最短路算法,面向边权非负的图(也可以处理,但是对于负边权,不保证路径长度上的拓扑序,时间复杂度很容易假)。
  • 是在 Bellman-Ford 算法的基础之上,加入了一点贪心思想
  • Bellman-Ford 算法的 \(i\) 循环严重超时了,为了减少 \(i\) 的循环次数,所以 dijkstra 算法出现了
  • 利用了路径长度的拓扑序
  • 每次对于 \(dp[i]\) 最小的 \(i\) 进行转移,可以使得每次 \(j\) 更新到的答案更加接近正确答案
  • 总共 \(O(n^2 + m)\)
  • 堆优化,总共 \(O(n + (m \log n))\)
  • 观察时间复杂度,发现当 \(n^2 \le m\) 的时候,不堆优化或许更快
模板 Code
void dijkstra(int s){
  priority_queue<asd, vector<asd>, cmp> a; // 小根堆
  for(int i = 0; i <= n; i++) dp[i] = 1e18;
  dp[s] = 0, a.push({0, s});
  while(!a.empty()){
    asd i = a.top();
    a.pop();
    if(i.x > dp[i.id]) continue; // 答案不够优秀,可以不进行转移
    for(qwe j : c[i.id]){
      if(dp[j.x] > dp[i.id] + j.w){
        dp[j.x] = dp[i.id] + j.w;
        a.push({dp[j.x], j.x}); // 这里会是 j.x 重复出现,但是对时间复杂度没有大影响
      }
    }
  }
}

dijkstra 优化 dp

实际上 dijkstra 复杂度的那个 \(m \log n\)\(m\) 在是松弛次数(题目:https://www.luogu.com.cn/problem/P6681 )。

所以 dijkstra 跑权在点上的图,松弛次数只有 \(O(n)\),复杂度 \(O(n + n \log n)\)

弗洛伊德(Floyd)算法

  • 一种 \(dp\),可计算边权不相同的图中任意两点之间的最短距离
  • 显然,\(O(n^3)\)
  • 温馨提示:如果你不能确定自己的 Floyd 循环顺序正确,那么你可以让你的 Floyd 连续跑 3 遍,那么这样你的 Floyd 一定正确(这我也不清楚为啥)

Floyd 算法正确性证明如下:

  • 你可以发现,最后枚举路径可以满足任意一条路径会不断扩张
  • 枚举第一个中点,任意两点之间的最短路径为 两点之间长 \(1\)最短路径
  • 枚举第二个中点,任意两点之间的最短路径为 两点之间长 \(1\)\(2\)最短路径
  • 枚举第三个中点,任意两点之间的最短路径为 两点之间长 \(1\)\(2\)\(3\)最短路径
  • 你可以将最短路径视作 \(dp\) 代表的值
  • 发现是依次顺延的,这就是拓扑序
  • 那么只要枚举中点的循环按照任意一个长 \(n\)全排列枚举都可以

实现

点击查看代码
const int Inf = 1e9;

for(int i = 1; i <= n; i++){
    for(int j = 1; j <= n; j++){
        dp[i][j] = 1ll * (i != j) * Inf;
    }
}
for(int i = 1, U, V, W; i <= m; i++){
    cin >> U >>V >> W;
    dp[U][V] = W, dp[V][U] = W; // 邻接矩阵
}
void Floyd(){
    for(int h = 1; h <= n; h++){ // 这 3 个循环必须按照顺序进行
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                if(i != j && i != h && j != h && dp[i][h] + dp[h][j] < dp[i][j]){
                    dp[i][j] = dp[i][h] + dp[h][j];
                }
            }
        }
    }
}

无向图的最小环问题

https://www.luogu.com.cn/problem/P6175

这道题可以很好检验你是否了解了 Floyd。

考虑 Floyd,则在枚举到中点 \(k\) 的时候,任意两点间 当前最短路径中经过的点编号在 \([1,k-1]\) 范围中。

所以枚举到每个中点的时候,枚举 \(i,j \in [1, k-1]\)。这样统计的正确性显然,这可以保证环上没有重复的点出现,因为这样枚举环上的点都是 \([1,k]\) 组成的。

点击查看代码
#include <bits/stdc++.h>

using namespace std;
using LL = long long;

const int MAXN = 100 + 3;

int n, m;
int eg[MAXN][MAXN];
int dp[MAXN][MAXN];

int main(){
  cin >> n >> m;
  for(int i = 1; i <= n; i++){
    for(int j = 1; j <= n; j++) eg[i][j] = 5e8 + 3, dp[i][j] = 5e8 + 3;
  }
  for(int i = 1, U, V, W; i <= m; i++){
    cin >> U >> V >> W;
    eg[U][V] = min(eg[U][V], W), eg[V][U] = min(eg[V][U], W);
  }
  int ans = 1e9;
  for(int i = 1; i <= n; i++){
    for(int j = 1; j <= n; j++) dp[i][j] = eg[i][j];
  }
  for(int k = 1; k <= n; k++){
    for(int i = 1; i < k; i++){
      for(int j = i + 1; j < k; j++){
        ans = min(ans, dp[i][j] + eg[i][k] + eg[k][j]);
      }
    }
    for(int i = 1; i <= n; i++){
      for(int j = 1; j <= n; j++) dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
    }
  }
  if(ans > 5e8){
    cout << "No solution.";
    return 0;
  }
  cout << ans;
  return 0;
}

SPFA 算法

面相任意边权的图,单源最短路。

本质是一种搜索!!!

  • 最优性剪枝:每个点只记录当前最短的路径长度。
  • 入队优化:点的最短路径长度被更新时,如果该点还在队列中,则不重复入队。

对于最短路径问题,宽搜会有更好的剪枝效果,深搜无法保证较低的时间复杂度。

每个点最坏入队 \(O(n)\) 次,对于随机图,期望 \(O(1)\) 次。

标准复杂度 \(O((n + m)n)\),对于随机图,期望复杂度 \(O(n + m)\)

普通 SPFA 算法

模板 Code
#include <bits/stdc++.h>

using namespace std;
using LL = long long; 
using PII = pair<int, int>;

const int MAXN = 5e5 + 3;

int n, m, S;
bool vis[MAXN];
LL l[MAXN];
queue<int> que;
vector<PII> eg[MAXN];

void Record(int x, LL L){
  if(l[x] > L){
    l[x] = L;
    if(!vis[x]){
      vis[x] = 1, que.push(x);
    }
  }
}

void SPFA(){
  for(int i = 1; i <= n; i++) l[i] = (1ll << 31) - 1;
  Record(S, 0);
  while(!que.empty()){
    int i = que.front();
    que.pop(), vis[i] = 0;
    for(PII e : eg[i]){
      Record(e.first, l[i] + e.second);
    }
  }
}

int main(){
  cin >> n >> m >> S;
  for(int i = 1, U, V, W; i <= m; i++){
    cin >> U >> V >> W;
    eg[U].push_back({V, W});
  }
  SPFA();
  for(int i = 1; i <= n; i++){
    cout << l[i] << " ";
  }
  return 0;
}

进化 SPFA

SLF 优化

SLF 优化 Code
#include <bits/stdc++.h> // SPFA + ios + O2 + SLF优化
/*
SLF(Small Label First) 优化:
将原队列改成双端队列,对要加入队列的点 p,
如果 dis[p] 小于队头元素 u 的 dis[u],
将其插入到队头,否则插入到队尾。
*/

using namespace std;
using LL = long long;
using PII = pair<int, int>;

const int MAXN = 5e5 + 3;

int n, m, S;
bool vis[MAXN];
LL l[MAXN];
deque<int> que;
vector<PII> eg[MAXN];

void Record(int x, LL L){
  if(l[x] > L){
    l[x] = L;
    if(!vis[x]){
      vis[x] = 1;
      if(!que.empty() && l[x] < l[que.front()]){
        que.push_front(x);
      }else{
        que.push_back(x);
      }
    }
  }
}

void SPFA(){
  for(int i = 1; i <= n; i++) l[i] = (1ll << 31) - 1;
  Record(S, 0);
  while(!que.empty()){
    int i = que.front();
    que.pop_front(), vis[i] = 0;
    for(PII e : eg[i]){
      Record(e.first, l[i] + e.second);
    }
  }
}

int main(){
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n >> m >> S;
  for(int i = 1, U, V, W; i <= m; i++){
    cin >> U >> V >> W;
    eg[U].push_back({V, W});
  }
  for(int i = 1; i <= n; i++){
    random_shuffle(eg[i].begin(), eg[i].end());
  }
  SPFA();
  for(int i = 1; i <= n; i++){
    cout << l[i] << " ";
  }
  return 0;
}

LLL 优化

SLF 优化 + LLL 优化 Code
#include <bits/stdc++.h> // SPFA + ios + O2 + SLF优化 + LLL优化
/*
SLF(Small Label First) 优化:
将原队列改成双端队列,对要加入队列的点 p,
如果 dis[p] 小于队头元素 u 的 dis[u],
将其插入到队头,否则插入到队尾。
*/
/*
LLL(Large Label Last) 优化:
对每个要出队的队头元素 u,比较 dist[u] 和队列中点的 dist 的平均值,
如果 dist[u] 更大,将其弹出放到队尾,然后取队首元素进行相同操作,
直到队头元素的 dist 小于等于平均值。
*/

using namespace std;
using LL = long long;
using PII = pair<int, int>;

const int MAXN = 5e5 + 3;

int n, m, S;
bool vis[MAXN];
LL l[MAXN], sum = 0;
deque<int> que;
vector<PII> eg[MAXN];

void Record(int x, LL L){
  if(l[x] > L){
    l[x] = L;
    if(!vis[x]){
      vis[x] = 1;
      if(!que.empty() && l[x] < l[que.front()]){
        que.push_front(x);
      }else{
        que.push_back(x);
      }
      sum += l[x];
    }
  }
}

void SPFA(){
  for(int i = 1; i <= n; i++) l[i] = (1ll << 31) - 1;
  Record(S, 0);
  while(!que.empty()){
    int i = que.front();
    if(l[i] > (que.empty() ? 1e18 : sum / int(que.size()))){
      que.pop_front(), que.push_back(i);
      continue;
    }
    sum -= l[i], que.pop_front(), vis[i] = 0;
    for(PII e : eg[i]){
      Record(e.first, l[i] + e.second);
    }
  }
}

int main(){
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n >> m >> S;
  for(int i = 1, U, V, W; i <= m; i++){
    cin >> U >> V >> W;
    eg[U].push_back({V, W});
  }
  for(int i = 1; i <= n; i++){
    random_shuffle(eg[i].begin(), eg[i].end());
  }
  SPFA();
  for(int i = 1; i <= n; i++){
    cout << l[i] << " ";
  }
  return 0;
}

极限随机化

思想来自 https://blog.51cto.com/u_15127499/2673860

SLF 优化 + 普通随机化 Code
#include <bits/stdc++.h> // SPFA + ios + O2 + SLF优化 + 随机化

using namespace std;
using LL = long long;
using PII = pair<int, int>;

const int MAXN = 5e5 + 3;

int n, m, S;
unsigned long long seed = time(0), ntime = seed;
bool vis[MAXN];
LL l[MAXN];
deque<int> que;
vector<PII> eg[MAXN];

int rnd(unsigned long long mod){ // 随机化
  seed <<= 13, seed %= 111145147, seed *= 19198107, seed >>= 17;
  seed += rand() % ntime, seed %= 180324927, seed += n + 73 * m + 1117 * S;
  return seed % mod;
}

void Record(int x, LL L){
  if(l[x] > L){
    l[x] = L;
    if(!vis[x]){
      vis[x] = 1;
      if(!que.empty() && l[x] < l[que.front()]){
        que.push_front(x);
      }else{
        que.push_back(x);
      }
    }
  }
}

void SPFA(){
  for(int i = 1; i <= n; i++) l[i] = (1ll << 31) - 1;
  Record(S, 0);
  while(!que.empty()){

    for(int www = 1; www <= 4 && que.size() > 1; www++){
      int x = rnd(que.size() - 1) + 1;
      if(l[que[0]] > l[que[x]]){
        swap(que[0], que[x]);
      }
    }

    int i = que.front();
    que.pop_front(), vis[i] = 0;
    for(PII e : eg[i]){
      Record(e.first, l[i] + e.second);
    }
  }
}

int main(){
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n >> m >> S;
  for(int i = 1, U, V, W; i <= m; i++){
    cin >> U >> V >> W;
    eg[U].push_back({V, W});
  }
  for(int i = 1; i <= n; i++){
    random_shuffle(eg[i].begin(), eg[i].end());
  }
  SPFA();
  for(int i = 1; i <= n; i++){
    cout << l[i] << " ";
  }
  return 0;
}
SLF 优化 + 极限随机化 Code

轻松 AC 洛谷 P4779 【模板】单源最短路径(标准版)

#include <bits/stdc++.h> // SPFA + ios + O2 + SLF优化 + 随机化

using namespace std;
using LL = long long;
using PII = pair<int, int>;

const int MAXN = 5e5 + 3;

int n, m, S;
unsigned long long seed = time(0), ntime = seed;
bool vis[MAXN];
LL l[MAXN];
deque<int> que;
vector<PII> eg[MAXN];

int rnd(unsigned long long mod){ // 随机化
  seed <<= 13, seed += rand() % ntime, seed += n + 73 * m + 1117 * S;
  return seed % mod;
}

void Record(int x, LL L){
  if(l[x] > L){
    l[x] = L;
    if(!vis[x]){
      vis[x] = 1;
      if(!que.empty() && l[x] < l[que.front()]){
        que.push_front(x);
      }else{
        que.push_back(x);
      }
    }
  }
}

void SPFA(){
  for(int i = 1; i <= n; i++) l[i] = (1ll << 31) - 1;
  Record(S, 0);
  while(!que.empty()){

    for(int www = 1; www <= 6 && que.size() > 1; www++){
      int x = rnd(que.size() - 1) + 1;
      if(l[que[0]] > l[que[x]]){
        swap(que[0], que[x]);
      }
    }
    for(int www = 1; www <= 6 && que.size() > www; www++){
      int x = www, y = que.size() - www;
      if(l[que[0]] > l[que[x]]){
        swap(que[0], que[x]);
      }
      if(l[que[0]] > l[que[y]]){
        swap(que[0], que[y]);
      }
    }

    int i = que.front();
    que.pop_front(), vis[i] = 0;
    for(PII e : eg[i]){
      Record(e.first, l[i] + e.second);
    }
  }
}

int main(){
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n >> m >> S;
  for(int i = 1, U, V, W; i <= m; i++){
    cin >> U >> V >> W;
    eg[U].push_back({V, W});
  }
  for(int i = 1; i <= n; i++){
    random_shuffle(eg[i].begin(), eg[i].end());
  }
  SPFA();
  for(int i = 1; i <= n; i++){
    cout << l[i] << " ";
  }
  return 0;
}

差分约束

不等式可 \(x - y < c\) 变形为 \(x < c + y\),这很像最短路不等式 \(l_x < l_y + c\),即更新到最短路不能再更新,所以有负环就是无解。

乘法运算可以取 \(\log\) 变为加法运算(https://www.luogu.com.cn/problem/P4926

模板 Code
#include <bits/stdc++.h>

using namespace std;
using LL = long long; 
using PII = pair<int, int>;

const int MAXN = 5e5 + 3;

int n, m, S, cnt[MAXN];
bool vis[MAXN];
LL l[MAXN];
queue<int> que;
vector<PII> eg[MAXN];

void Record(int x, LL L){
  if(l[x] > L){
    l[x] = L;
    if(!vis[x]){
      cnt[x]++, vis[x] = 1, que.push(x);
      if(cnt[x] >= n){
        cout << "NO";
        exit(0);
      }
    }
  }
}

void SPFA(){
  for(int i = 0; i <= n; i++) l[i] = (1ll << 31) - 1;
  Record(0, 0);
  while(!que.empty()){
    int i = que.front();
    que.pop(), vis[i] = 0;
    for(PII e : eg[i]){
      Record(e.first, l[i] + e.second);
    }
  }
}

int main(){
  cin >> n >> m;
  for(int i = 1, U, V, W; i <= m; i++){
    cin >> U >> V >> W;
    eg[V].push_back({U, W});
  }
  for(int i = 1; i <= n; i++) eg[0].push_back({i, 0});
  SPFA();
  for(int i = 1; i <= n; i++){
    cout << l[i] << " ";
  }
  return 0;
}

Johnson 算法

介绍

  • 是一个全源最短路算法,面向任意边权的图,时间复杂度 \(O(nm + n + (m\log n))\),即:差分约束 + dijkstra
  • 对于无负边权的图,求全源最短路,可以跑 \(n\) 遍 dijkstra,但是负边权呢?
  • 可以跑 \(n\) 遍 SPFA !但是 \(O(n^2 m)\) 太不划算了。
  • 思考如何使得负边权消失
模板 Code
#include <bits/stdc++.h>

using namespace std;
using LL = long long; 
using PII = pair<int, int>;

const int MAXN = 5e5 + 3;

struct Node{
  int x;
  LL l;
  bool operator< (Node j) const {
    return l > j.l;
  }
};

int n, m, cnt[MAXN];
bool vis[MAXN];
LL w[MAXN], dis[MAXN];
queue<int> que;
vector<PII> eg[MAXN];

void Record(int x, LL L){
  if(w[x] > L){
    w[x] = L;
    if(!vis[x]){
      cnt[x]++, vis[x] = 1, que.push(x);
      if(cnt[x] >= n){
        cout << -1; // 有负环 
        exit(0);
      }
    }
  }
}
void SPFA(){
  for(int i = 0; i <= n; i++) w[i] = (1ll << 31) - 1;
  Record(0, 0);
  while(!que.empty()){
    int i = que.front();
    que.pop(), vis[i] = 0;
    for(PII e : eg[i]){
      Record(e.first, w[i] + e.second);
    }
  }
}

int main(){
  ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for(int i = 1, U, V, W; i <= m; i++){
    cin >> U >> V >> W;
    eg[U].push_back({V, W});
  } // w[U] - w[V] + W >= 0
    // w[U] + W >= w[V]
    // w[V] <= w[U] + W
  for(int i = 1; i <= n; i++) eg[0].push_back({i, 0});
  SPFA();
  for(int S = 1; S <= n; S++){
    for(int i = 1; i <= n; i++) dis[i] = 1e9;
    priority_queue<Node> pq;
    dis[S] = 0, pq.push({S, 0});
    while(!pq.empty()){
      Node i = pq.top();
      pq.pop();
      if(dis[i.x] < i.l) continue;
      for(PII e : eg[i.x]){
        int nxt = e.first, nw = dis[i.x] + e.second + w[i.x] - w[nxt];
        if(dis[nxt] > nw){
          dis[nxt] = nw, pq.push({nxt, nw});
        }
      }
    }
    LL ans = 0;
    for(int i = 1; i <= n; i++){
      LL _ans = dis[i] - w[S] + w[i];
      ans += 1ll * i * (dis[i] >= 1e9 ? 1e9 : _ans);
    }
    cout << ans << "\n";
  }
  return 0;
}

次短路

非严格次短路:https://www.luogu.com.cn/problem/P1491

严格次短路:https://www.luogu.com.cn/problem/P2865

对于非严格次短路,复杂度 \(O(n(n+m)n)\) 的:

  • 先求出任意一条最短路。
  • 枚举删除最短路上的哪一条边,删除后再跑一遍最短路,答案去最小值。
点击查看代码
#include <bits/stdc++.h>

using namespace std;
using LL = long long;
using PII = pair<int, int>;

const int MAXN = 200 + 3, MAXM = 20000 + 3;

double ew[MAXM];
int to[MAXM], tot = 0, start[MAXN], top[MAXN], nex[MAXM];

inline void ADDeg(int u, int v, double w){
	tot++, to[tot] = v, ew[tot] = w, nex[top[u]] = tot;
	start[u] = (!start[u] ? tot : start[u]), top[u] = tot;
}

void No(){ cout << -1; exit(0); }

int n, m;
PII a[MAXN];

int from[MAXN], frome[MAXN], vis[MAXN];
double dp[MAXN];

double SPFA(){
	queue<int> que;
	for(int i = 1; i <= n; i++) dp[i] = 1e9, from[i] = 0, vis[i] = 0;
	dp[1] = 0, que.push(1);
	while(!que.empty()){
		int i = que.front();
		que.pop(), vis[i] = 0;
		for(int e = start[i]; e > 0; e = nex[e]){
			double nw = dp[i] + ew[e];
			if(nw < dp[to[e]]){
				dp[to[e]] = nw, from[to[e]] = i, frome[to[e]] = e;
				if(!vis[to[e]]){
					vis[to[e]] = 1, que.push(to[e]);
				}
			}			
		}
	}
	return dp[n];
}

int main(){
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= n; i++){
		cin >> a[i].first >> a[i].second;
	}
	for(int i = 1, U, V; i <= m; i++){
		cin >> U >> V;
		double w = sqrt((a[U].first - a[V].first) * (a[U].first - a[V].first) + (a[U].second - a[V].second) * (a[U].second - a[V].second));
		ADDeg(U, V, w), ADDeg(V, U, w);
	}
	vector<int> vt;
	if(SPFA() >= 1e9) No();
	for(int x = n; x > 1; x = from[x]){
		vt.push_back(frome[x]);
	}
	double ans = 1e9;
	for(int e : vt){
		double tmp0 = 1e9, tmp1 = 1e9;
		swap(tmp0, ew[e]), swap(tmp1, ew[e^1]);
		ans = min(ans, SPFA());
		swap(tmp0, ew[e]), swap(tmp1, ew[e^1]);
	}
	if(ans >= 1e9) No();
	cout << fixed << setprecision(2) << ans;
	return 0;
}

对于严格次短路或非严格次短路,复杂度 \(O(n + m \log n)\) 的:

  • dijkstra,dp 状态存两个值,分情况更新。
posted @ 2023-10-17 21:24  hhhqx  阅读(4)  评论(0编辑  收藏  举报