最短路

最短路

下文的 1 设为起点。

Dijkstra

是个单源最短路。

思路

把状态设计为 (当前这个点,长度),那么只有当当前长度是从 1 开始走到当前点的最短路,才能做转移,于是可以记录任何一个点到 1 的最小距离,不断更新这个最短距离,然后对于这条最短路开始转移即可,由于是用当前点的最短路,所以得用堆。由于是一个贪心的选边,所以遇到负权值就不灵了,应为当前点不一定和堆头最靠近。

代码

选自 P4779 单源最短路径

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>

using namespace std;

const int MaxN = 1e5 + 10;

struct S {
    int v, w;

    bool operator<(const S &j) const {
        return w > j.w;
    }
};

int n, m, s;
int d[MaxN];
bool vis[MaxN];
vector<S> g[MaxN];
priority_queue<S> q;

void Record(int u, int v, int w) {
    if (d[v] <= d[u] + w) {
        return;
    }
    d[v] = d[u] + w;
    q.push({v, d[v]});
}

int main() {
    cin >> n >> m >> s;
    for (int i = 1, u, v, w; i <= m; i++) {
        cin >> u >> v >> w;
        g[u].push_back({v, w});
    }
    fill(d + 1, d + n + 1, 1e9);
    for (Record(0, s, 0); !q.empty();) {
        int u = q.top().v; // 用最优的来更新其它点
        q.pop();
        if (vis[u]) { // 得在下面标记,不然不是最优解,只有开始扩展了的才能说是已经最优的了
            continue;
        }
        vis[u] = 1;
        for (S i : g[u]) {
            Record(u, i.v, i.w);
        }
    }
    for (int i = 1; i <= n; i++) {
        cout << d[i] << " ";
    }
    return 0;
}

时间复杂度

\(O(mlogn)\)

空间复杂度

\(O(n + m)\)

Spfa

Spfa它死了是一个可以处理负权值的时间复杂度极不稳定的算法。

思路

感觉和 Dijkstra 没什么区别,但是它并不是用最短路去更新,是不断地用队头去更新别的点,如果更短就更新,不过由于当前点的最短路不一定就是队头的方案,所以要将自己标记为未走过,而被松弛的点要标记为走过

代码

选自 P3385 负环

#include <algorithm>
#include <iostream>
#include <queue>

using namespace std;
using ll = long long;

const int MaxN = 2e3 + 10;

struct Edge {
  int to, nxt, w;
} e[MaxN << 2];

struct S {
  ll x, w;
};

ll d[MaxN], t, n, m;
int head[MaxN], cnt[MaxN], tot;
bool vis[MaxN], flag;
queue<S> q;

bool Record(int u, int v, int w) {
  if (d[v] <= d[u] + w) {
    return 0;
  }
  d[v] = d[u] + w;
  if (vis[v]) {
    return 0;
  }
  vis[v] = 1;
  q.push({v, d[v]});
  return ++cnt[v] > n;
}

bool spfa() {
  fill(d + 1, d + n + 1, 1e10);
  fill(vis + 1, vis + n + 1, 0);
  fill(cnt + 1, cnt + n + 1, 0);
  queue<S>().swap(q);
  for (flag = 0, Record(0, 1, 0); !q.empty() && !flag;) {
    S u = q.front();
    q.pop();
    vis[u.x] = 0;
    for (int i = head[u.x]; ~i; i = e[i].nxt) { // caoshurui 大巨佬教我的方法:~i
      Record(u.x, e[i].to, e[i].w) && (flag = 1);
    }
  }
  return flag;
}

int main() {
  for (cin >> t; t; t--) {
    cin >> n >> m;
    fill(head + 1, head + n + 1, -1);
    tot = 0;
    for (int i = 1, u, v, w; i <= m; i++) {
      cin >> u >> v >> w;
      e[++tot] = {v, head[u], w};
      head[u] = tot;
      if (w >= 0) {
        e[++tot] = {u, head[v], w};
        head[v] = tot;
      }
    }
    cout << (spfa() ? "YES" : "NO") << endl;
  }
  return 0;
}

时间复杂度

最坏:\(O(n^2)\)

平均:\(O(mlogn)\)

空间复杂度

\(O(n+m)\)

势能 Dijkstra

弥补了普通 Dijkstra 的缺点的快速算法,即可以运行负权值的 Dijkstra。

思路

其实就是把负权值用一种方法变成正的。下列都用 \(\color{black}ABC237E\ \ Skiing\) 的思路来讲。

原本的边权是 \(w_{uv} = h_u - h_v\)\(w_{uv}=(h_u - h_v) \times 2\),现在加上一个势能变为:\(w_{uv}+h_u-h_v\),这个势能可以理解为减掉一部分后又加上了更多的一部分,那么那段减掉的就被抵消了,从而从负边权变成了正的,那么从点 1 到点 n 的权值从:\(w_{1v_1}+w_{v_1v_2}+ \dots + w_{v_xv_n}\)变成了:\((w_{1v_1}+h_1-h_{v_1})+(w_{v_1v_2}+h_{v_1}-h_{v_2})+ \dots + (w_{v_xv_n}+h_{v_x} - h_n)=w_{1v_1}+w_{v_1v_2}+ \dots + w_{v_xv_n}+h_1-h_n\)

由于本题是求最长路,可以将它取反求最短路。

那么边权就可以推出来了:

  • 如果 \(h_u > h_v\)

    那么边权为 \(h_u - h_v\),取反为 \(h_v - h_u\),加上势能为:\(h_v - h_u + h_u - h_v = 0\)

  • 如果 \(h_u < h_v\)

    那么边权为 \(2(h_u-h_v)\),取反为 \(2(h_v - h_u)\),加上势能为:\(2h_v-2h_u+h_u-h_v=h_v-h_u > 0\)

边权都为正,可以跑 Dijkstra。

势能一般可以用 Spfa 从 0 点跑到其他点的最短路,求出来,虽然说感觉有点鸡肋,但在\(\color{black}这道题\)中只能这么做

代码

选自 ABC237E Skiing

#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

const int MaxN = 2e5 + 10;

struct Node {
  int v;
  long long d;

  bool operator<(const Node &j) const {
    return d > j.d;
  }
};

int n, m;
vector<int> g[MaxN];
long long d[MaxN], h[MaxN], ans;
priority_queue<Node> q;
bool vis[MaxN];

void Record(int u, int v, long long w) {
  if (d[v] <= d[u] + w) {
    return;
  }
  d[v] = d[u] + w;
  q.push({v, d[v]});
}

int main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> h[i];
    d[i] = 1e18;
  }
  for (int i = 1, u, v; i <= m; i++) {
    cin >> u >> v;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  for (Record(0, 1, 0); !q.empty();) {
    Node u = q.top();
    q.pop();
    if (vis[u.v]) {
      continue;
    }    
    vis[u.v] = 1;
    for (int i : g[u.v]) {
      if (h[u.v] > h[i]) {
        Record(u.v, i, 0);
      } else {
        Record(u.v, i, h[i] - h[u.v]);
      }
    }
  }
  for (int i = 1; i <= n; i++) {
    (d[i] != -1e18) && (ans = max(ans, -(d[i] - (h[1] - h[i]))));
  }
  cout << ans << endl;
  return 0;
}

时间复杂度

\(O(mlogn)\)

空间复杂度

\(O(n + m)\)

posted @ 2023-05-17 20:44  yabnto  阅读(23)  评论(0编辑  收藏  举报