E043. Нахождение отрицательного цикла в グラフе

e-maxx algorithm original: C/C++ #algorithm #emaxx #graph
選択した UI 言語に合わせて問題文をロシア語から翻訳します。コードは変更しません。

Источник: e-maxx.ru/algo, страница PDF 132.

Дан ориентированный взвешенный グラフ

с

vertexми и

рёбрами. it is required find в нём любой

цикл отрицательного веса, если таковой имеется. При другой постановке задачи — it is required find все пары вершин такие, что между ними существует путь сколько угодно малой длины. Эти два варианта задачи удобно решать разными アルゴリズムами, поэтому ниже будут рассмотрены оба из них. Одна из распространённых "жизненных" постановок этой задачи — следующая: известны курсы валют, т.е. курсы перевода из одной валюты в другую. it is required узнать, можно ли некоторой последовательностью обменов получить выгоду, т.е. стартовав с одной единицы какой-либо валюты, получить в итоге больше чем одну единицу этой же валюты.

解法 с помощью アルゴリズムа Форда-Беллмана

Bellman-Ford algorithm позволяет проверить наличие или отсутствие цикла отрицательного веса в グラフе, а при его наличии — find один из таких циклов. Не будем вдаваться здесь в подробности (которые описаны в статье по アルゴリズムу Форда-Беллмана), а приведём лишь итог — то, как работает アルゴリズム.

Делается

итераций アルゴリズムа Форда-Беллмана, и если на последней итерации не произошло никаких изменений — то отрицательного цикла в グラフе нет. В противном случае возьмём вершину, расстояние до которой изменилось, и будем идти от неё по предкам, пока не войдём в цикл; этот цикл и будет искомым отрицательным циклом. 実装:

struct edge {

int a, b, cost;

};

int n, m;

vector<edge> e;

const int INF = 1000000000;

void solve() {

vector<int> d (n);

vector<int> p (n, -1);

int x;
for (int i=0; i<n; ++i) {

x = -1;

for (int j=0; j<m; ++j)
if (d[e[j].b] > d[e[j].a] + e[j].cost) {

d[e[j].b] = max (-INF, d[e[j].a] + e[j].cost);

p[e[j].b] = e[j].a;

x = e[j].b;

} }

if (x == -1)

cout << "No negative cycle found.";

else {

int y = x;
for (int i=0; i<n; ++i)

y = p[y];

vector<int> path;

for (int cur=y; ; cur=p[cur]) {

path.push_back (cur);

if (cur == y && path.size() > 1)  break;

}

reverse (path.begin(), path.end());

cout << "Negative cycle: ";

for (size_t i=0; i<path.size(); ++i)

cout << path[i] << ' ';

} }

解法 с помощью アルゴリズムа Флойда-Уоршелла

Floyd-Warshall algorithm позволяет решать вторую постановку задачи — когда надо find все пары вершин , между которыми кратчайшего пути не существует (т.е. он имеет бесконечно малую величину). Опять же, более подробные объяснения содержатся в описании アルゴリズムа Флойда-Уоршелла, а здесь мы приведём только итог. После того, как Floyd-Warshall algorithm отработает для 入力ного グラフа, переберём все пары вершин

, и

для каждой такой пары проверим, бесконечно мал 最短経路 из

в

или нет. Для этого переберём третью

вершину

, и если для неё оказалось

(т.е. она лежит в цикле отрицательного веса), а сама она достижима

из

и из неё достижима

— то путь

может иметь бесконечно малую длину. 実装:

for (int i=0; i<n; ++i)
for (int j=0; j<n; ++j)
for (int t=0; t<n; ++t)
if (d[i][t] < INF && d[t][t] < 0 && d[t][j] < INF)

d[i][j] = -INF;

Задачи в online judges

Список задач, в которых it is required искать цикл отрицательного веса:

● UVA #499 "Wormholes" [Complexity: низкая]

● UVA #104 "Arbitrage" [Complexity: средняя]

● UVA #10557 "XYZZY" [Complexity: средняя]

C# 解法

自動ドラフト、提出前に確認
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    struct edge {
            int a, b, cost;
    };
    int n, m;
    vector<edge> e;
    const int INF = 1000000000;
    void solve() {
            List<int> d (n);
            List<int> p (n, -1);
            int x;
            for (int i=0; i<n; ++i) {
                    x = -1;
                    for (int j=0; j<m; ++j)
                            if (d[e[j].b] > d[e[j].a] + e[j].cost) {
                                    d[e[j].b] = max (-INF, d[e[j].a] + e[j].cost);
                                    p[e[j].b] = e[j].a;
                                    x = e[j].b;
                            }
            }
            if (x == -1)
                    Console.WriteLine( "No negative cycle found.";
            else {
                    int y = x;
                    for (int i=0; i<n; ++i)
                            y = p[y];
                    List<int> path;
                    for (int cur=y; ; cur=p[cur]) {
                            path.push_back (cur);
                            if (cur == y && path.size() > 1)  break;
                    }
                    reverse (path.begin(), path.end());
                    Console.WriteLine( "Negative cycle: ";
                    for (size_t i=0; i<path.size(); ++i)
                            Console.WriteLine( path[i] << ' ';
            }
    }
    for (int i=0; i<n; ++i)
            for (int j=0; j<n; ++j)
                    for (int t=0; t<n; ++t)
                            if (d[i][t] < INF && d[t][t] < 0 && d[t][j] < INF)
                                    d[i][j] = -INF;
}

C++ 解法

照合済み/オリジナル
struct edge {
        int a, b, cost;
};
int n, m;
vector<edge> e;
const int INF = 1000000000;
void solve() {
        vector<int> d (n);
        vector<int> p (n, -1);
        int x;
        for (int i=0; i<n; ++i) {
                x = -1;
                for (int j=0; j<m; ++j)
                        if (d[e[j].b] > d[e[j].a] + e[j].cost) {
                                d[e[j].b] = max (-INF, d[e[j].a] + e[j].cost);
                                p[e[j].b] = e[j].a;
                                x = e[j].b;
                        }
        }
        if (x == -1)
                cout << "No negative cycle found.";
        else {
                int y = x;
                for (int i=0; i<n; ++i)
                        y = p[y];
                vector<int> path;
                for (int cur=y; ; cur=p[cur]) {
                        path.push_back (cur);
                        if (cur == y && path.size() > 1)  break;
                }
                reverse (path.begin(), path.end());
                cout << "Negative cycle: ";
                for (size_t i=0; i<path.size(); ++i)
                        cout << path[i] << ' ';
        }
}
for (int i=0; i<n; ++i)
        for (int j=0; j<n; ++j)
                for (int t=0; t<n; ++t)
                        if (d[i][t] < INF && d[t][t] < 0 && d[t][j] < INF)
                                d[i][j] = -INF;

Java 解法

自動ドラフト、提出前に確認
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    struct edge {
            int a, b, cost;
    };
    int n, m;
    vector<edge> e;
    const int INF = 1000000000;
    void solve() {
            ArrayList<Integer> d (n);
            ArrayList<Integer> p (n, -1);
            int x;
            for (int i=0; i<n; ++i) {
                    x = -1;
                    for (int j=0; j<m; ++j)
                            if (d[e[j].b] > d[e[j].a] + e[j].cost) {
                                    d[e[j].b] = max (-INF, d[e[j].a] + e[j].cost);
                                    p[e[j].b] = e[j].a;
                                    x = e[j].b;
                            }
            }
            if (x == -1)
                    System.out.println( "No negative cycle found.";
            else {
                    int y = x;
                    for (int i=0; i<n; ++i)
                            y = p[y];
                    ArrayList<Integer> path;
                    for (int cur=y; ; cur=p[cur]) {
                            path.push_back (cur);
                            if (cur == y && path.size() > 1)  break;
                    }
                    reverse (path.begin(), path.end());
                    System.out.println( "Negative cycle: ";
                    for (size_t i=0; i<path.size(); ++i)
                            System.out.println( path[i] << ' ';
            }
    }
    for (int i=0; i<n; ++i)
            for (int j=0; j<n; ++j)
                    for (int t=0; t<n; ++t)
                            if (d[i][t] < INF && d[t][t] < 0 && d[t][j] < INF)
                                    d[i][j] = -INF;
}

Материал разбит как アルゴリズムическая 問題: изучить постановку, понять асимптотику и реализовать アルゴリズム на выбранном языке.

Vacancies for this task

有効な求人 with overlapping task tags are 表示.

すべての求人
有効な求人はまだありません。