「解题报告」UOJ32 [UR #2] 跳蚤公路
图论好难啊。
首先明确题目要求的其实就是从 \(1\) 到 \(u\) 是否能够经过一个负环。首先容易得到如果存在负环,那么一定存在一个简单负环,所以只需要考虑简单环。
考虑如何判断负环:Floyd 和 Bellman-Ford。
为什么不用 SPFA
______,___。
Bellman-Ford 这么好写为什么不写。
Floyd 复杂度太高了,考虑 Bellman-Ford。正常 Bellman-Ford 是设 \(f_{i, u}\) 表示从 \(1\) 到 \(u\) 走最多 \(i\) 步的最短路,那么假如存在某个 \(f_{n, u} < f_{n - 1, u}\),那么说明存在负环。如果 \(x\) 固定,我们就容易找出负环,那么能过经过负环的点就是这个 \(u\) 能到达的所有点。
容易发现,对于任意一条路径,我们可以将其长度写为 \(kx+b\) 的形式。而由于我们只考虑简单环,那么 \(k \in [-n, n]\),对于某个 \(k\) 来说,我们肯定只考虑 \(b\) 最小的路径。那么我们可以将上面的东西拓展一维,设 \(f_{i, u, k}\) 表示从 \(1\) 到 \(u\) 走最多 \(i\) 步,路径长 \(kx+b\) 中 \(b\) 的最小值,那么 \(i\) 到 \(u\) 的最小值就是 \(\min_k f_{i, u, k}\)。那么存在负环的条件就是 \(\min_k kx + f_{n, u, k} < \min_j jx + f_{n - 1, u, j}\)。对于某个点可以经过负环的 \(x\) 的取值范围,就是所有能够到达这个点的负环的取值范围的并集。
把 \(\min\) 拆开,相当于对每个 \(k\) 求 \(kx + f_{n, u, k} < \min_j jx + f_{n - 1, u, j}\)。求 \(kx + f_{n, u, k} < \min_j jx + f_{n - 1, u, j}\) 的取值范围,相当于对每个 \(j\) 求 \(kx + f_{n, u, k} < jx + f_{n - 1, u, j}\) 的取值范围的交集,直接求即可。
最后得到每个点的并集之后,取个反即可。细节很多,有点难实现,具体实现看代码。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105, MAXM = 10005;
const long long INF = 0x3f3f3f3f3f3f3f3f;
int n, m;
int u[MAXM], v[MAXM], w[MAXM], s[MAXM];
long long f[MAXN][MAXN][MAXN << 1];
bool vis[MAXN];
vector<int> e[MAXN];
void dfs(int u) {
vis[u] = 1;
for (int v : e[u]) if (!vis[v]) {
dfs(v);
}
}
long long floordiv(long long a, long long b);
long long ceildiv(long long a, long long b);
long long floordiv(long long a, long long b) {
if (!a) return 0;
if (b < 0) a *= -1, b *= -1;
if (a > 0) return a / b;
else return -ceildiv(-a, b);
}
long long ceildiv(long long a, long long b) {
if (!a) return 0;
if (b < 0) a *= -1, b *= -1;
if (a > 0) return (a + b - 1) / b;
else return -floordiv(-a, b);
}
vector<pair<long long, long long>> range[MAXN];
void chkmin(long long &a, long long b) {
a = min(a, b);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%d%d%d%d", &u[i], &v[i], &w[i], &s[i]);
e[u[i]].push_back(v[i]);
}
memset(f, 0x3f, sizeof f);
for (int i = 0; i < n; i++) {
chkmin(f[i][1][MAXN], 0);
for (int k = -n; k <= n; k++) {
for (int j = 1; j <= m; j++) if (f[i][u[j]][k + MAXN] < INF) {
chkmin(f[i + 1][v[j]][k + MAXN + s[j]], f[i][u[j]][k + MAXN] + w[j]);
}
}
}
for (int v = 1; v <= n; v++) {
for (int i = 1; i <= n; i++) {
vis[i] = 0;
}
dfs(v);
for (int k = -n; k <= n; k++) if (f[n][v][k + MAXN] < INF) {
long long l = -INF, r = INF;
for (int j = -n; j <= n; j++) if (f[n - 1][v][j + MAXN] < INF) {
long long p = f[n - 1][v][j + MAXN] - f[n][v][k + MAXN];
if (k - j > 0) {
r = min(r, ceildiv(p, k - j) - 1);
} else if (k - j == 0) {
if (p <= 0) {
l = INF, r = -INF;
break;
}
} else {
l = max(l, floordiv(p, k - j) + 1);
}
}
if (l <= r) {
for (int i = 1; i <= n; i++) if (vis[i])
range[i].push_back({l, r});
}
}
}
for (int i = 1; i <= n; i++) {
if (range[i].empty()) {
printf("-1\n");
continue;
}
long long ans = 0;
sort(range[i].begin(), range[i].end(), [](auto a, auto b) { return a.second < b.second; });
for (int j = range[i].size() - 1; j; j--) {
range[i][j - 1].first = min(range[i][j - 1].first, range[i][j].first);
}
ans += range[i].front().first - (-INF);
ans += INF - range[i].back().second;
for (int j = 1; j < range[i].size(); j++) {
ans += max(range[i][j].first - range[i][j - 1].second - 1, 0ll);
}
if (ans > 1000000000000000000ll) ans = -1;
printf("%lld\n", ans);
}
return 0;
}