图上的环和最长路

1. 判环存在性、判连通图

并查集可以做到 \(O(n + m)\) 判环存在性、判断图是否为连通图。

\(O(n + m)\) 的 dfs 或 bfs 也可以做到判环存在性。

\(O(n + m)\) 的 TopoSort 甚至可以找到有向图的环。

总之环的存在性是一个很好解决的问题。

HDOJ 3342 Legal or Not https://acm.hdu.edu.cn/showproblem.php?pid=3342

题意:

给一个 \(N\) 个点 \(N\) 条边的有向图,第 \(i(1 \leq M)\) 条边从 \(a_i\) 连向 \(b_i\) ,询问该图是否无环。

\(2 \leq N, M \leq 100, 0 \leq a_i, b_i \leq N - 1\)

题解:

dfs 或 并查集 可以以 \(O(n + m)\) 判断环的存在性。

有向图也可以使用 TopoSort 以 \(O(n + m)\) 顺便判环。

std::vector<std::vector<int> > adj(N + 1);
std::vector<int> ind(N + 1);
for (int i = 1; i <= M; i++) {
    int u, v; std::cin >> u >> v;
    u++; v++;
    adj[u].push_back(v);
    ind[v]++;
}
std::queue<int> que;
for (int i = 1; i <= N; i++) if (ind[i] == 0) que.push(i);

std::queue<int> S;
while (!que.empty()) {
    int x = que.front();
    que.pop();
    S.push(x);
    for (auto y : adj[x]) {
        if (--ind[y] == 0)
            que.push(y);
    }
}
std::cout << (S.size() == N ? "YES" : "NO") << "\n";

时间复杂度 \(O(N + M)\)

HDU 1325 Is It A Tree? https://acm.hdu.edu.cn/showproblem.php?pid=1325

题意:

给一个有向图,判断是否是一棵树。

题解:

如果一个图是一棵树,则有且仅有一个节点入读为 0 ,其他节点入度为一。

反之不能确定一个图是一棵树,因为图可能是一个独立点与一个环。

DAG 允许一个儿子有多个父亲,树只允许一个儿子有一个父亲。于是树是 DAG 的真子集。

所以用并查集判断图是否是连通图(只有一个连通块)。

再然后。

若整个图有且仅有一个节点的入度为 0 ,其他节点的入度为 1 。则是一棵树。

或者可以判断点数是变数加 1 ,且所有点入度不超过 1 。

但不能用并查集判环。因为即使是 DAG 也不一定是树。

这里数据很大(题目不给范围),需要离散化。

const int MAXN = 200005;
int fa[MAXN];
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
void solve() {
    int Case = 0;
    int x, y;
    std::vector<std::array<int, 2> > op;
    std::set<int> used;
    std::vector<int> numx;
    while (std::cin >> x >> y) {
        if (x == -1 && y == -1) break;
        else if (x == 0 && y == 0) {
            ++Case;
            for (auto x : used) numx.push_back(x);
            std::sort(numx.begin(), numx.end());
            numx.erase(std::unique(numx.begin(), numx.end()), numx.end());
            
            int n = numx.size(), m = op.size();
            for (int i = 0; i <= n; i++) fa[i] = i;
            std::vector<std::vector<int> > adj(n + 1);
            std::vector<int> ind(n + 1);
            
            int ok = 1;
            for (int i = 0; i < m; i++) {
                int su = op[i][0], sv = op[i][1];
                int u = std::lower_bound(numx.begin(), numx.end(), su) - numx.begin() + 1;
                int v = std::lower_bound(numx.begin(), numx.end(), sv) - numx.begin() + 1;
                adj[u].push_back(v);
                ind[v]++;
                if (find(u) == find(v)) {
                    ok = 0;
                }
                fa[find(u)] = find(v);
            }
            
            int cnt = 0;
            for (int i = 1; i <= n; i++) if (find(i) == i) cnt++;
            ok &= cnt == 1;

            int f0 = 0, f1 = 0;
            for (int i = 1; i <= n; i++) {
                f0 += ind[i] == 0;
                f1 += ind[i] == 1;
            }
            ok &= f0 == 1 && f1 == std::max(0, (int)used.size() - 1);

            std::cout << "Case " << Case << " is " << (ok ? "" : "not ") << "a tree.\n";
            op.clear();
            used.clear();
            numx.clear();
        }
        else {
            op.push_back({x, y}), used.insert({x, y});
        }
    }
}

时间复杂度 \(O(N + M)\)

2. 找环

找环一般没法使用简单的搜索做到,因为搜索在保证复杂度的情况下,不能保证点的遍历顺序。而且图是存在松弛这种性质的。

2.1 有向图找环

TopoSort 的余图是环。否则不是环。

2.2 无向图找环

暂时没学会可以找出无向图的所有点满足这些点属于环的办法。

3. 最小/大环

3.1 无权图最大环

注意到任意两点 \(u, v\) 若是环上相邻。

无向图至少会为解集加入两个答案 \(dep[u] - dep[v] + w\)\(dep[v] - dep[u] + w\) 。其中只有一个是正确答案。

若是有向图,至少 dfs 到点的先后顺序会严重影响答案。

无权图中最大环一定可以被 dfs 找到,没有反例。

无权图 dfs 的解集不全是环,但极大解是最大环。最小环不具有这种性质。

一遍 \(O(n + m)\) 的 dfs 能且仅能找出无权图的最大环长。

有办法通过 dfs 拿出某个最大环。

证伪。反例:
n = 4, m = 5 。(1, 3), (1, 4), (1, 2), (2, 3), (3, 4) 。顺序构建无向图后,最大环长为 \(3\) ,应该是 \(4\)

POJ 3895 http://poj.org/problem?id=3895

题意:

找出一个无权图的最大环长。

题解:

从任意点开始做一次 \(O(n + m)\) 的 dfs ,维护深度。

环上的点被访问量大于一次,基于此更新答案。

无环图 dfs 要记录前驱,否则会重复访问前驱。

const int MAXN = 2E5+10;
int n, m;
std::vector<int> adj[MAXN];
int dep[MAXN];
int ans;
void dfs(int x, int pre) {
    int m = adj[x].size();
    for (int i = 0; i < m; i++) if (adj[x][i] != pre) {
        int y = adj[x][i];
        if (dep[y] == -1) {
            dep[y] = dep[x] + 1;
            dfs(y, x);
        }
        else {
            ans = std::max(ans, dep[x] - dep[y] + 1);
        }
    }
};
void solve() {
    std::cin >> n >> m;
    for (int i = 1; i <= n; i++) adj[i].clear(), dep[i] = -1;
    for (int i = 1; i <= m; i++) {
        int u, v; std::cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dep[1] = 0;
    ans = 0;
    dfs(1, -1);
    std::cout << ans << "\n";
}

3.2 一般图最小环

Dijkstra

基于事实1:枚举到的某条边一定会在最小环上。

\(O(M)\) 枚举边,假设是 \(u\) 连向 \(v\) 。反向边的边权为 \(w\)
\(dis\_new(u, v)\) 是删除当前边后 \(u\)\(v\) 的最短路径。

无负权图从 \(u\) 跑一次 Dijkstra 。维护 \(dis\_new(u, v) + w\) 。总时间复杂度 \(O(m(n + m) \log m)\)

有负权图从 \(u\) 跑一次 Bellman-ford 。维护 \(dis\_new(u, v) + w\) 。总时间复杂度 \(O(n m^{2})\) (显然这个算法任何情况下都用不到)。

如何优雅地删去一条边?标记一下无向边 \(e(u, v)\) ,遍历到时跳过。

显然有办法通过 Dijkstra 拿出某个最小环。

HDOJ 6005 https://acm.hdu.edu.cn/showproblem.php?pid=6005 (2016 CCPC final)

题意:

给一个无向带权图,询问最小环大小。如果没有环输出 0 。

给出 \(m\) 条边,第 \(i(1 \leq i \leq m)\) 给出 \((x_1, y_1), (x_2, y_2), w\) 表示从 \((x_1, y_1)\) 有一条连向 \((x_2, y_2)\) 的权值为 \(w\) 的无向边。

\(1 \leq m \leq 4000, -1000 \leq x_i, y_i \leq 10000, 1 \leq w \leq 10^{5}\)

题解:

问题一:如何把网格点转化为邻接矩阵?开一个 map 维护 rk 。邻接链表的去重不重要。

问题二:如何得到无向图最小环?只给出 \(m\)\(n\) 可以视为同级。\(n^{3}\) 做法不可行。可以使用 \(O(m (n+m) \log m)\) 的 Dijikatra 求最小环做法。

const int MAXM = 4005;
std::array<int, 3> E[MAXM];
struct Node {
    int v, w;
    int nxt;
} list[MAXM * 2];
int head[MAXM * 2];
inline void edgeadd(int u, int v, int w) {
    list[++head[0]] = {v, w, head[u]};
    head[u] = head[0];
}
int rk, u, v, w_uv, ans;
int Dijikstra (int s, int t) {
    std::vector<int> dist(rk + 1, inf);
    dist[s] = 0;
    std::priority_queue<std::array<int, 2> > S;
    S.push({-dist[s], s});
    while (!S.empty()) {
        int x = S.top()[1];
        S.pop();
        if (dist[x] + w_uv > ans) return dist[t];
        if (dist[x] == inf || x == t) return dist[x];
        for (int i = head[x]; i != 0; i = list[i].nxt) {
            int y = list[i].v, w = list[i].w;
            if (x == u && y == v) continue;
            if (x == v && y == u) continue;
            if (dist[x] + w < dist[y]) {
                dist[y] = dist[x] + w;
                S.push({-dist[y], y});
            }
        }
    }
    return dist[t];
}
void solve() {
    int m; std::cin >> m;
    std::map<std::array<int, 2>, int > point;
    rk = 0;
    for (int i = 1; i <= m; i++) {
        int x1, y1, x2, y2, w; std::cin >> x1 >> y1 >> x2 >> y2 >> w;
        if (!point.count({x1, y1})) point[{x1, y1}] = ++rk;
        if (!point.count({x2, y2})) point[{x2, y2}] = ++rk;
        u = point[{x1, y1}], v = point[{x2, y2}];
        E[i] = {u, v, w};
    }
    for (int i = 0; i <= rk; i++) head[i] = 0;
    for (int i = 1; i <= m; i++) {
        int u = E[i][0], v = E[i][1], w = E[i][2];
        edgeadd(u, v, w);
        edgeadd(v, u, w);
    }
    ans = inf;
    for (int i = 1; i <= m; i++) {
        u = E[i][0], v = E[i][1], w_uv = E[i][2];
        int dis = Dijikstra(u, v);
        if (dis != inf)
            ans = std::min(ans, dis + w_uv);
    }
    if (ans == inf) ans = 0;
    std::cout << ans << "\n";
}

时间复杂度 \(O(m (n + m) \log m)\)

Floyd

大多时候最小环问题中,Floyd 比 Dijkstra 常用。

基于事实1的基础上。

基于事实2: Folyd 在 \([1, k)\) 范围内的 \(dist\) 是最短路。于是可以在 \(k\) 上 DP 或更新。

有向图:最小环至少两个点或一条边

枚举到 \(k\) 时,确定 \(i < k\)\(w_{k, i}\) 存在,用 \(dist_{i, k} + w_{k, j}\) 维护最小环。\(dist_{i, k}\) 经过点 \(k\) ,点 \(k\) 提供另一条边。

HDOJ 7322 https://acm.hdu.edu.cn/showproblem.php?pid=7322 (2023 杭电多校)

题意:

给一个有向带权图。找到最小环长。并计数最小环,输出 \(\bmod 998244353\) 的结果。

\(1 \leq n \leq 500, 1 \leq m \leq \frac{n(n + 1)}{2}, 1 \leq w \leq 10^{9}\)

题解:

定理: \(\{1, 2, 3, \cdots, n\}\) 的所有子集显然可以被划分为 \(\{1\}, \{1, 2\}, \{1, 2, 3\}, \cdots, \{1, 2, 3, \cdots, n\}\)

这是 Floyd 不重不漏的合理性。

最小环不难找,每轮 \(k\) 迭代结束后,维护 \(dsit_{i, k} + w_{k, j}\)

首先可以维护 \(cnt_{i, j}\) 表示 \(i, j\) 的最短路条数。在 Folyd 上基于乘法原理合并,\(cnt_{i, j} = cnt_{i, k} \times cnt_{k, j}\)

更新到每一个 \(k\) ,枚举 \(i \in [1, k)\) ,询问 \(dist_{i, k} + w_{k, i}\) 。尝试询问当前最小环,并维护。

  • 如果有更小环,更新计数器。

  • 若是最小环,计数器增加 \(cnt_{i, k}\)

const int MAXN = 505;
const int MOD = 998244353;
ll G[MAXN][MAXN];
ll dist[MAXN][MAXN];
ll cnt[MAXN][MAXN];
void solve() {
    int n, m; std::cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (i == j) G[i][i] = 0;
            else G[i][j] = llinf;
        }
    }
    for (int i = 1; i <= m; i++) {
        int u, v, w; std::cin >> u >> v >> w;
        if (w < G[u][v])
            G[u][v] = w, cnt[u][v] = 1;
        else if (w == G[u][v])
            ++cnt[u][v];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            dist[i][j] = G[i][j];
        }
    }
    ll mi = llinf, rescnt = 0;
    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (dist[i][k] == inf || dist[k][j] == inf) continue;
                if (dist[i][k] + dist[k][j] < dist[i][j]) {
                    dist[i][j] = dist[i][k] + dist[k][j];
                    cnt[i][j] = cnt[i][k] * cnt[k][j] % MOD;
                }
                else if (dist[i][k] + dist[k][j] == dist[i][j]) {
                    cnt[i][j] = (cnt[i][j] + cnt[i][k] * cnt[k][j] % MOD) % MOD;
                }
            }
        }
        for (int i = 1; i < k; i++) {
            if (dist[i][k] == llinf || G[k][i] == llinf) continue;
            if (mi > dist[i][k] + G[k][i]) {
                mi = dist[i][k] + G[k][i];
                rescnt = cnt[i][k];
            }
            else if (mi == dist[i][k] + G[k][i]) {
                rescnt = (1LL * rescnt + cnt[i][k]) % MOD;
            }
        }
    }
    if (mi == llinf) mi = -1, rescnt = -1;
    std::cout << mi << " " << rescnt << "\n";

无向图:最小环至少三个点或三条边。

枚举到 \(k\) 时,确定 \(i < j < k\)\(w_{i, k}, w_{k, j}\) 存在,用 \(dist_{i, j} + w_{i, k} + w_{k, j}\) 维护最小环。\(dist_{i, j}\) 不经过点 \(k\) ,点
\(k\) 独立提供两条边。

HDOJ 1599 https://acm.hdu.edu.cn/showproblem.php?pid=1599

题意:

杭州有 \(N\) 个景区,有 \(M\) 条双向的路连接景区。第 \(i(1 \leq i \leq M)\) 条路连接景区 \(a_i, b_i\) ,花费 \(c_i\)

询问一条旅游路线,从某景区 \(A\) 出发,最终回到 \(A\) 景区,且至少经过两个不同的其他景区,并且花费最少。

\(N \leq 100, M \leq 1000, c \leq 100\)

题解

无向图找最小环问题,边需要去重。

跑 Floyd 过程中,\([1, k)\) 的最短路可以被确定,若 \(i \neq j\)\(i, j\) 可达 \(k\) ,维护 \(dist_{i, j} + w_{i, k} + w_{k, j}\)

保证 \(i, j\)\(check\) 时还没被更新。

const int MAXN = 105;
int G[MAXN][MAXN], dist[MAXN][MAXN];
void solve() {
    int n, m;
    while (std::cin >> n >> m) {
        // std::vector<std::vector<int> > G(n + 1, std::vector<int>(n + 1, inf));
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                G[i][j] = inf;
        for (int i = 0; i <= n; i++) G[i][i] = 0;
        for (int i = 1; i <= m; i++) {
            int u, v, w; std::cin >> u >> v >> w;
            G[u][v] = std::min(w, G[u][v]);
            G[v][u] = std::min(w, G[v][u]);
        }
        // std::vector<std::vector<int> > dist(n + 1, std::vector<int>(n + 1, inf));
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                dist[i][j] = G[i][j];
        // dist = G;
        int ans = inf;
        for (int k = 1; k <= n; k++)
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= n; j++) {
                    if (i < k && j < k && i != j && dist[i][j] != inf && G[i][k] != inf && G[k][j] != inf)
                        ans = std::min(ans, dist[i][j] + G[i][k] + G[k][j]);
                    if (dist[i][k] != inf && dist[k][j] != inf && dist[i][k] + dist[k][j] < dist[i][j])
                        dist[i][j] = dist[i][k] + dist[k][j];
                }
        if (ans != inf) std::cout << ans << "\n";
        else std::cout << "It's impossible.\n";
    }
}

时间复杂度 \(O(n^{3})\)

POJ 1734 http://poj.org/problem?id=1734

题意:

给一个无向带权图,按任意一种连通顺序输出任意一个最小环。

\(n \leq 100, m \leq 10000\)

题解:

一是如何用 Floyd 得到最小环。可以维护出最小环时候环内的三个点。让 \(i < j < k\)

则情况 dist 数组,重跑 FLoyd 能确保正确复杂度。每轮的 check 和要保证 \([1, k)\) 的 dist 数组还未更新。

怎么拿出某个最小环?路径用 \(pre[i][j]\) 记录 \(k\) 代表 \(i \rightarrow k \rightarrow j\) 。递归到叶子时回溯输出。

const int MAXN = 105;
int pre[MAXN][MAXN];
int G[MAXN][MAXN], dist[MAXN][MAXN];
std::vector<int> cir;
void find (int l, int r) {
    int last = pre[l][r];
    if (last == -1) {
        cir.push_back(r);
        return;
    }
    find(l, last);
    find(last, r);
};
void solve() {
    int n, m;
    while (std::cin >> n >> m) {
        cir.clear();
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++) {
                if (i != j) G[i][j] = inf;
                else G[i][j] = 0;
                pre[i][j] = -1;
            }
        for (int i = 0; i <= n; i++) G[i][i] = 0;
        for (int i = 1; i <= m; i++) {
            int u, v, w; std::cin >> u >> v >> w;
            G[u][v] = std::min(w, G[u][v]);
            G[v][u] = std::min(w, G[v][u]);
        }
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                dist[i][j] = G[i][j];
        int ans = inf, c1 = -1, c2 = -1, c3 = -1;
        for (int k = 1; k <= n; k++)
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= n; j++) {
                    if (i < j && j < k && dist[i][j] != inf && G[i][k] != inf && G[k][j] != inf) {
                        if (ans > dist[i][j] + G[i][k] + G[k][j]) {
                            ans = dist[i][j] + G[i][k] + G[k][j];
                            c1 = k, c2 = i, c3 = j;
                        }
                    }
                    if (dist[i][k] != inf && dist[k][j] != inf && dist[i][k] + dist[k][j] < dist[i][j])
                        dist[i][j] = dist[i][k] + dist[k][j];
                }
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                dist[i][j] = G[i][j];
        for (int k = 1; k <= n; k++) {
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= n; j++) {
                    if (k == c1 && i == c2 && j == c3) {
                        cir.push_back(c2);
                        find(c2, c3);
                        cir.push_back(c1);
                    }
                }
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= n; j++) {
                    if (dist[i][k] != inf && dist[k][j] != inf && dist[i][k] + dist[k][j] < dist[i][j])
                        dist[i][j] = dist[i][k] + dist[k][j], pre[i][j] = k;
                }
        }
        if (ans == inf) std::cout << "No solution.\n";
        else for (int i = 0; i < cir.size(); i++) std::cout << cir[i] << " \n"[i == (int)cir.size() - 1];
    }
}

时间复杂度 \(O(n^{3})\)

带负权图无向图的最大环,即反权图的最小环。

5. 正环/负环

正环即反权图上找负环。负环跑 Bellman-ford 迭代轮数 \(\geq N\)

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

题意:
给一个 \(n\) 个点 \(m\) 条边的有向图。询问是否存在从 \(1\) 出发能到达的负环。

\(1 \leq n \leq 2 \times 10^{3}, 1 \leq m \leq 3 \times 10^{3}, 1 \leq u, v \leq n, -10^{4} \leq w \leq 10^{4}, 1 \leq T \leq 10\)

题解:

\(1\) 跑 Bellman-ford 的第 \(N\) 轮,如果依然有边可以被松弛,则从 \(1\) 出发存在负环。

一轮就可以拿出负环的证明:

负环被确定后。每个点到源点的距离一定小于上一轮的距离。否则这个点不在负环上。

    int n, m; std::cin >> n >> m;
    std::vector<std::array<int, 3> > E;
    for (int i = 1; i <= m; i++) {
        int u, v, w; std::cin >> u >> v >> w;
        if (w >= 0) {
            E.push_back({u, v, w});
            E.push_back({v, u, w});
        } else {
            E.push_back({u, v, w});
        }
    }
    int M = E.size();
    std::vector<ll> dis(n + 1, llinf);
    dis[1] = 0;
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < M; j++) {
            int u = E[j][0], v = E[j][1], w = E[j][2];
            if (dis[u] == llinf) continue;
            if (dis[u] + w < dis[v]) {
                dis[v] = dis[u] + w;
            }
        }
    }
    std::vector<int> negative(n + 1);
    for (int j = 0; j < M; j++) {
        int u = E[j][0], v = E[j][1], w = E[j][2];
        if (dis[u] == llinf) continue;
        if (dis[u] + w < dis[v]) {
            dis[v] = dis[u] + w;
            negative[v] = 1;
        }
    }
    int ok = 0;
    for (int i = 1; i <= n; i++) ok |= negative[i] > 0;
    std::cout << (ok ? "YES" : "NO") << "\n";

时间复杂度 \(O(nm)\)

6. DAG 最长路

P1807 最长路 https://www.luogu.com.cn/problem/P1807

题意:

给一个有向无环带权图,询问节点 \(1\) 到节点 \(n\) 的最长距离。

\(1 \leq n \leq 1500, 1 \leq m \leq 5 \times 10^{4}, 1 \leq u, v \leq n, -10^{5} \leq w \leq 10^{5}\)

题解:

\(O(n + m)\) 对 DAG 的反权图 TopoSort ,然后在 Topo 序上完成最短路的松弛操作。

结果的负数即最长路。

    int n, m; std::cin >> n >> m;
    std::vector<std::vector<std::array<int, 2> > > adj(n + 1);
    std::vector<int> ind(n + 1);
    for (int i = 1; i <= m; i++) {
        int u, v, w; std::cin >> u >> v >> w;
        adj[u].push_back({v, -w});
        ind[v]++;
    }
    std::queue<int> S, L;
    for (int i = 1; i <= n; i++) if (ind[i] == 0) S.push(i);
    while(!S.empty()) {
        int x = S.front();
        S.pop();
        L.push(x);
        for (auto info : adj[x]) {
            int y = info[0];
            if (!--ind[y])
                S.push(y);
        }
    }
    std::vector<ll> dp(n + 1, llinf);
    dp[1] = 0;
    while (!L.empty()) {
        int u = L.front();
        L.pop();
        if (dp[u] == llinf) continue;
        for (auto info : adj[u]) {
            int v = info[0], w = info[1];
            dp[v] = std::min(dp[v], dp[u] + w);
        }
    }
    std::cout << (dp[n] == llinf ? -1 : -dp[n]) << "\n";

时间复杂度 \(O(n + m)\)

**无负权图最长路可以转 DAG **

无负权无向图,要么有正环,要么是链 + 零环。

无负权有向图,要么有正环,要么可以转 DAG 。

7. 一般图最长路

不限制是 DAG / 无负权图。则反权图上跑 Bellman-ford 。

https://atcoder.jp/contests/abc061/tasks/abc061_d

题意:

给一个一般图,询问 1 到 n 的最长路。

如果没有负环,从 1 跑反权图的 Bellman-ford 。

如果 1 在一个负环内,拿出这个负环。

基于这个负环,迭代出负环可达的点,若可达 n 。则最长路无限长。

    int N, M; std::cin >> N >> M;
    std::vector<std::array<int, 3> > E(M + 1);
    for (int i = 1; i <= M; i++) {
        int u, v, c; std::cin >> u >> v >> c;
        E[i] = {u, v, c};
    }
    std::vector<std::array<int, 3> > anti_E(E.begin(), E.end());
    for (int i = 1; i <= M; i++) anti_E[i][2] *= -1;
    
    std::vector<ll> dist(N + 1, llinf);
    dist[1] = 0;
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= M; j++) {
            int u = anti_E[j][0], v = anti_E[j][1], c = anti_E[j][2];
            if (dist[u] == llinf) continue;
            if (dist[u] + c < dist[v]) {
                dist[v] = dist[u] + c;
            }
        }
    }
    
    std::vector<int> negative(N + 1);
    std::vector<int> can(N + 1);
    for (int j = 1; j <= M; j++) {
        int u = anti_E[j][0], v = anti_E[j][1], c = anti_E[j][2];
        if (dist[u] == llinf) continue;
        if (dist[u] + c < dist[v])
            negative[v] = can[v] = 1;
    }
    for (int i = 1; i <= N - 1; i++) {
        for (int j = 1; j <= M; j++) {
            int u = anti_E[j][0], v = anti_E[j][1], c = anti_E[j][2];
            if (dist[u] == llinf) continue;
            if (can[u])
                can[v] = 1;
        }
    }
    
    if (can[N]) std::cout << "inf\n";
    else std::cout << -dist[N] << "\n";

posted @ 2024-05-08 20:25  zsxuan  阅读(38)  评论(0编辑  收藏  举报