有负权图上的最短路算法 (Goldberg, 1995)
最近听说有了一个有负权图上的
注意: 本人主要是向论文学习了关键性质, 细节部分可能有所不同.
我们知道, OI 中常用的在负权图上的 Bellman–Ford 算法可以在
pricing
这是本文涉及的第一个技术, 其实也就是大家熟知的最短路重赋权方法. 如果我们有一个
那么新边权
是非负的, 我们可以使用经典的 Dijkstra 算法求解. 显然在没有负环的时候, 这样的
但是这一转化的另一点好处在于, 我们求出这一
scaling
这是本文涉及的第二个技术, 这个对于学过弱多项式复杂度费用流的同学来说可能比较熟悉了. 对于一张图
则必有
简单算法
我们先把目前
- 对于
内部的边, 显然没有影响, 因为两端的 值变化量相同. - 对于
连出的边的边权会减少 , 但是注意到连出的边此时皆为正的, 这不会产生新的 边, 但有可能会加入新的 边. - 对于连入
的边, 边权加 , 这说明连入 的负边会全部消失, 这是这个操作的关键用途.
如果我们将
不幸的是, 每次操作之后, 图的结构会有不小的改变, 因为正权边变小之后会产生一些新的
但事实上, 我们距离最终的算法已相去不远, 这只需要我们用到一点组合观察, 并针对性的设计上述的操作方式.
最终算法
我们记有
注意: 为了后续论述的正确性, 这里的可达性需要定义为:
反链
对于
链
对于
在还完全没有进行操作的时候, 显然我们是有
但是有没有可能我们操作
这个操作相对而言维护起来就需要一点技巧了, 需要动态检查哪些边降为了
Dilworth
现在你有一个链上的算法, 你有一个反链上的算法. 现在让我们来过最后一关.
由 Dilworth 定理, 链和反链必有一者
什么, 你问轮数为啥是 ?
经过
轮之后一定完成.
等等, 我们是不是还得够快的找到这么一条链/反链啊?
在 DAG 上 DP 可以求出每个点连出的最长链, 然后对所有极大点构成的反链进行检查即可, 这样即可在
综上, 一轮总共时间为
实现
如下代码可以通过 LuoguP5960 差分约束算法.
#include <bits/stdc++.h>
using namespace std;
const int V = 5005, E = 5005;
int N, M;
int u[E], v[E], w[E];
int p[V];
bool mark[V];
vector<pair<int, int>> G[V], G2[V];
int t, N2;
stack<int> stk;
int dfn[V], low[V], belongs[V], topo[V];
void tarjan(int u) {
dfn[u] = low[u] = ++t; stk.push(u);
for (const auto &e : G[u]) {
int v = e.first, w = e.second + p[u] - p[v];
if (w > 0) continue;
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (belongs[v] == -1) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
topo[N2++] = u;
int v;
do {
v = stk.top(); stk.pop();
belongs[v] = u;
} while (v != u);
}
}
bool SCC() {
t = N2 = 0;
memset(dfn, 0, sizeof(dfn));
memset(mark, 0, sizeof(mark));
memset(belongs, -1, sizeof(belongs));
for (int i = 0; i != N; ++i) G2[i].clear();
for (int i = 0; i != N; ++i) if (!dfn[i]) tarjan(i);
#ifdef ELEGIA_DEBUG
int marks = 0;
#endif // ELEGIA_DEBUG
for (int i = 0; i != N; ++i) for (const auto &e : G[i]) {
int j = e.first, w = e.second + p[i] - p[j];
if (w < 0) {
if (belongs[i] == belongs[j]) return true;
mark[belongs[i]] = true;
#ifdef ELEGIA_DEBUG
++marks;
#endif // ELEGIA_DEBUG
}
G2[belongs[i]].emplace_back(belongs[j], w);
}
#ifdef ELEGIA_DEBUG
cerr << "marks: " << marks << endl;
#endif // ELEGIA_DEBUG
return false;
}
int vis[V], pref[V], sec[V], len[V], chn[V];
vector<int> delay[V];
void chain(int start) {
int len = 0;
while (start != -1) {
chn[++len] = start;
start = sec[start];
}
memset(vis, 0, sizeof(vis));
queue<int> q;
for (int i = len; i; --i) {
int s = chn[i];
for (const auto &e : G2[s]) {
int v = e.first, w = e.second;
if (vis[v] || w >= 0) continue;
vis[v] = i;
q.push(v);
}
for (int u : delay[i]) if (!vis[u]) {
vis[u] = i;
q.push(u);
}
delay[i].clear();
while (!q.empty()) {
int u = q.front(); q.pop();
for (const auto &e : G2[u]) {
int v = e.first, w = e.second;
if (w > 0) {
if (w < i) delay[i - w].push_back(v);
continue;
}
if (vis[v]) continue;
vis[v] = i;
q.push(v);
}
}
}
}
bool refine() {
while (true) {
{
bool flag = false;
for (int u = 0; u != N; ++u) {
for (const auto &e : G[u]) {
int v = e.first, w = e.second + p[u] - p[v];
if (w < 0) {
flag = true;
break;
}
}
if (flag) break;
}
if (!flag) return false;
}
if (SCC()) return true;
memset(len, 0, sizeof(len));
memset(pref, -1, sizeof(pref));
memset(sec, -1, sizeof(sec));
int longest = -1;
for (int i = 0; i != N2; ++i) {
int u = topo[i];
for (const auto &e : G2[u]) {
int v = e.first, w = e.second;
if (w > 0) continue;
if (pref[v] != -1 && len[v] > len[u]) {
len[u] = len[v];
pref[u] = pref[v];
}
}
if (mark[u]) {
int opt = 0;
for (const auto &e : G2[u]) {
int v = e.first, w = e.second;
if (w >= 0) continue;
if (pref[v] != -1 && len[v] > opt) {
opt = len[v];
sec[u] = pref[v];
}
}
if (++opt > len[u]) {
len[u] = opt;
pref[u] = u;
} else sec[u] = -1;
}
if (longest == -1 || len[u] > len[longest])
longest = u;
}
int size = 0;
queue<int> q;
memset(vis, 0, sizeof(vis));
for (int i = N2 - 1; i >= 0; --i) {
int u = topo[i];
if (vis[u] || !mark[u]) continue;
++size;
for (const auto &e : G2[u]) {
int v = e.first, w = e.second;
if (!vis[v] && w < 0) {
vis[v] = true;
q.push(v);
}
}
while (!q.empty()) {
int u = q.front(); q.pop();
for (const auto &e : G2[u]) {
int v = e.first, w = e.second;
if (!vis[v] && w <= 0) {
vis[v] = true;
q.push(v);
}
}
}
}
#ifdef ELEGIA_DEBUG
cerr << "antichain: " << size << ", chain: " << len[longest] << endl;
#endif // ELEGIA_DEBUG
if (size < len[longest]) chain(longest);
for (int i = 0; i != N; ++i)
p[i] -= vis[belongs[i]];
}
}
bool priceScaling() {
int L = 0;
for (int i = 0; i != M; ++i)
while ((1 << L) <= -w[i]) ++L;
memset(p, 0, sizeof(p));
for (int d = L - 1; d >= 0; --d) {
for (int i = 0; i != N; ++i) G[i].clear();
for (int i = 0; i != N; ++i) p[i] *= 2;
for (int i = 0; i != M; ++i)
G[u[i]].emplace_back(v[i], -((-w[i]) >> d));
if (refine()) return true;
#ifdef ELEGIA_DEBUG
cerr << "finish scaling 2^" << d << endl;
#endif // ELEGIA_DEBUG
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
cin >> N >> M;
for (int i = 0; i != M; ++i) {
cin >> v[i] >> u[i] >> w[i];
--u[i]; --v[i];
}
if (priceScaling()) {
cout << "NO\n";
} else for (int i = 0; i != N; ++i)
cout << p[i] << " \n"[i == N - 1];
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何给本地部署的DeepSeek投喂数据,让他更懂你
· 超详细,DeepSeek 接入PyCharm实现AI编程!(支持本地部署DeepSeek及官方Dee
· 用 DeepSeek 给对象做个网站,她一定感动坏了
· .NET 8.0 + Linux 香橙派,实现高效的 IoT 数据采集与控制解决方案
· DeepSeek处理自有业务的案例:让AI给你写一份小众编辑器(EverEdit)的语法着色文件