Добрый день сообщество CF.
Я начал изучать потоки и столкнулся с проблемами. Недавно я нашёл алгоритм максимального потока минимальной стоимости.
Алгоритм
1) Для каждого ребра, соединяющего вершины u и v добавим обратное, соединяющее вершины v и u, с пропускной способностью равной нулю и стоимостью, равной минус стоимости прямого ребра.
2) Введем для каждой вершины u потенциал φu и расстояние от источника du. Пусть сначала они будут равны нулю.
3) Для каждого ребра e (в том числе и для обратного), соединяющего вершины u и v, введем вес weight(e) = cost(e) + φu — φv (именно для определения веса и нужны потенциалы — без них вес мог бы быть отрицательным для обратных ребер и нельзя было бы применить алгоритм Дейкстры), где cost(e) — стоимость ребра.
4) С помощью алгоритма Дейкстры находим расстояния от источника до всех вершин (в поиске участвуют только те ребра, поток по которым не максимальный). Если нет пути от источника до стока, значит максимальный поток найден и можно завершить алгоритм. Если есть — идем дальше.
5) Выставляем потенциалы для каждой вершины равными расстояниям от источника, которые нашли на предыдущем шаге. 6) Восстанавливаем кратчайший путь.
7) Находим максимальный дополняющий поток на этом пути.
8) Пропускаем найденный поток через кратчайший путь. Когда поток пропускается через ребро, то через соответствующее обратное (или прямое) ребро пропускается поток равный по модулю, но противоположный по значению. Таким образом, обратное ребро характеризует, на сколько можно уменьшить поток через прямое ребро, и какая от этого будет польза.
9) Итерация алгоритма закончена, идем на шаг 3.
Вот моя реализация:
#include<iostream> /// программа на случай неориентированного графа
#include<cstdio>
#include<cmath>
#include<vector>
#include<fstream>
using namespace std;
int p[103]; /// массив предков для восстановления кратчайших путей
int dist[103]; /// массив кратчайших путей из точки истока
int fi[103]; /// массив потециалов
bool flag[103];
int last; /// номер последнего элемента в куче
int n,m; /// кол-во вершин и рёбер в графе
int start,finish; /// start - точка истока finish - точка стока
struct kucha
{
int v,c; /// v - номер вершины, c - длина пути
};
kucha heap[100003]; /// куча для Дейкстры
struct rebro
{
int v; /// вершина в которую ведёт ребро
int flow; /// пропускная способность ребра
int rflow; /// кол-во единиц потока уже проходящих через это ребро
int cost; /// стоимость ребра в случае использования, не зависит от кол-ва потока через него
};
vector < vector <rebro> > g; /// способ хранения графа
void add(int x,int y,int flow,int cost) /// добавляем ребро
{
rebro z;
z.v=y;
z.flow=flow;
z.cost=cost;
z.rflow=0;
g[x].push_back(z);
z.flow=0;
z.cost=-cost;
z.v=x;
g[y].push_back(z);
}
int maxflow; /// max поток
int mincost; /// min цена
int main()
{
int x,y,fl,cos;
int i,j;
scanf("%d %d",&n,&m);
scanf("%d %d",&start,&finish);
g.resize(n);
start--;
finish--;
for(i=0;i<m;i++) /// чтение графа
{
scanf("%d %d %d %d",&x,&y,&fl,&cos); /// fl - пропускная способность ребра cos - его стоимость
x--;
y--;
add(x,y,fl,cos);
add(y,x,fl,cos);
}
int yy;
int addflow; /// добавочный поток
while(true)
{
for(i=0;i<n;i++)
dist[i]=1000000000;
for(i=0;i<n;i++)
flag[i]=false;
for(i=0;i<2*m;i++)
heap[i].c=1000000000;
dist[start]=0;
heap[1].v=start;
heap[1].c=0;
last=1;
while(last>0) /// Дейкстра с потенциалами
{
x=heap[1].v;
if(!flag[x])
{
flag[x]=true;
for(i=0;i<g[x].size();i++)
if(g[x][i].rflow<g[x][i].flow && !flag[g[x][i].v])
{
if(dist[g[x][i].v]>dist[x]+g[x][i].cost+fi[x]-fi[g[x][i].v])
{
dist[g[x][i].v]=dist[x]+g[x][i].cost+fi[x]-fi[g[x][i].v];
p[g[x][i].v]=x;
yy=dist[g[x][i].v];
last++;
j=last;
while(j>1 && yy<heap[j>>1].c)
{
heap[j]=heap[j>>1];
j>>=1;
}
heap[j].c=yy;
heap[j].v=g[x][i].v;
}
}
}
j=1;
while(j*2<last && (heap[last].c>heap[j*2].c || heap[last].c>heap[j*2+1].c))
{
if(heap[j*2].c<=heap[j*2+1].c)
{
heap[j]=heap[j*2];
j*=2;
}
else
{
heap[j]=heap[j*2+1];
j*=2;
j++;
}
}
heap[j]=heap[last--];
}
if(dist[finish]==1000000000) /// если нет пути выходим из алгоритма
break;
for(i=0;i<n;i++) /// расстановка потенциалов
fi[i]=dist[i];
x=finish;
addflow=1000000000;
while(x!=start) /// восстановление кратчайшего пути и поиск добавочного потока
{
for(i=0;i<g[p[x]].size();i++)
if(g[p[x]][i].v==x && g[p[x]][i].flow!=0)
{
addflow=min(addflow,g[p[x]][i].flow-g[p[x]][i].rflow);
break;
}
x=p[x];
}
maxflow+=addflow;
x=finish;
while(x!=start) /// сколько придётся заплатить за увеличение потока + увеличение потока
{
for(i=0;i<g[p[x]].size();i++)
if(g[p[x]][i].v==x && g[p[x]][i].flow!=0)
{
if(g[p[x]][i].rflow==0)
mincost+=g[p[x]][i].cost;
g[p[x]][i].rflow+=addflow;
for(j=0;j<g[x].size();j++)
if(g[x][j].v==p[x])
{
g[x][j].rflow-=addflow;
break;
}
break;
}
x=p[x];
}
}
cout<<maxflow<<endl;
cout<<mincost<<endl;
return 0;
} /// самый серьёзный тест на котором я проверял правильность
/*
5 6
1 5
1 3 3 5
1 4 1 1
1 2 4 10
2 4 5 1
3 4 2 2
4 5 3 1
*/
У меня остались вопросы:
По реализации:
1) Возможно удобнее хранить граф не vector < vector > g; , а rebro g[MAXN][MAXN]; ( матрица смежности ) + vector < vector > gg; (список смежности для скорости)?
2) Может можно избавиться от rflow в struct rebro?
С радостью выслушаю способы оптимизации моего кода.
По алгоритму:
1) Время работы O(n*m*log(n))?
2) Как быть если ограничен поток в вершине и задана её стомость?
3) Пригоден ли вообще такой алгоритм для случая, когда стоимоть ребра задана ввиде d денежных единиц на единицу потока и пропускная способность ребра >=1 ? Если да, то как это сделать?
Не могу найти задач на CF, которые могли бы проверить мою реализацию алгоритма на правильность. Пожалуйста, подскажите парочку таких на CF. Зааранее благодарю тех, кто поможет. Извиняюсь за большой текст.
Upd: Спасибо всем тем, кто ответил: CountZero,Miras321.
Особенно благодарен cmd,Perlik.
Благодаря их помощи:
1) заработал алгоритм 2) я понял, что решают задачи на потоки.
http://codeforces.net/contest/237/problem/E
Во-первых, алгоритм неверен, а именно обновление потенциалов не совсем так производится.
Так верно. Еще если в графе изначально есть ребра отрицательного веса, то в самом начале нужно один раз запустить Форда-Беллмана, чтобы выставить потенциалы. Также замечу, что этот алгоритм не работает, если в графе есть циклы отрицательного веса.
Что касается реализации: нет, rflow вы не уберете. Еще у вас очень неэффективное проталкивание — каждый раз просматривать весь список, чтобы найти нужное ребро — это странно немного. Я обычно пишу так: завожу вектор ребер, а в матрице смежности int g[n][n] просто храню номер ребра в этом большом списке, так удобнее на мой взгляд (хотя и лишняя память, да).
Время работы далеко не такое классное. На самом деле это O(поиск кратчайшего пути*количество итераций). Поскольку итераций в худшем |F|, где F — величина искомого потока, а ваша дейкстра работает за O(M*log(n)), то получаем O(|F|*M*log(N)). На практике же обычно чуть получше. А вообще, я не знаю за сколько он работает, в Кормене к сожалению эта тема не освещена. Но такой вариант должен проходить в любой задаче на поток. Вообще, я не знаю хорошей русскоязычной литературы конкретно по этому алгоритму (а вот статьи на английском годные гуглите). Вычеркивание циклов есть, например, в Седжвике.
Если поток ограничен в вершине, то просто делаете из вершины v две вершины v1 и v2. Все входящие ребра кидаете в v1, выходящие из v2. Между v1 и v2 проводите ребро заданной пропускной способности и стоимости.
В тренировках есть контест Миланина на потоки. Там задача H в чистом виде то, что вам нужно.
P.S. Ах да, зачем писать кучу вручную, если в С++ есть уже
priority_queue
P.P.S. И что касается 3 вопроса, то алго именно это и делает, либо я вопрос не понял.
Уточню 3 вопрос: у нас есть ребро, задана его пропускная способность d, и стоимость c. Если пропустить через ребро поток величины d, то мы заплатим c*d, если d-1, то c*(d-1), и так до нуля. Мой алгоритм предназначен для того случая, когда мы можем пускать поток величины 1<=x<=d и при любой величине x мы один раз заплатим с.
Первый пункт алгоритма создаёт циклы отрицательной длины. Так что-же по-вашему получается, алгоритм уничтожает самого себя?
"для того случая, когда мы можем пускать поток величины 1<=x<=d и при любой величине x мы один раз заплатим с"
это NP-полная задача
Не уверен. Вот задача которая так не решится.
«Трубопроводы»
Условие Имеется сеть трубопроводов по перекачке нефти, при этом участки трубопроводов имееют пропускную способность (тонна/час) и стоимость транспортировки (транзита) тонны нефти по участку. Необходимо организовать перекачку максимального количества нефти при минимальных суммарных затратах на транзит из пункта А в пункт В. Если два пункта X и Y связаны трубопроводом, то по этому трубопроводу можно перекачивать нефть как из X в Y, так и с Y в X .
Входные данные Входные данные задаются в файле input.txt. · В первой строке находится четыре натуральных числа, задающих сеть трубопроводов: N(1 < N ≤ 100) – количество пунктов, M ( 0 ≤ M ≤N(N-1)/2 ) – количество трубопроводов, номера пунктов A и B. · Каждая из сдедующих M строк содержит по 4 числа, задающих информацию о некотором трубопроводе: первые два числа – это номера пунктов связанных трубопроводом, его пропускная способность Pi (0 ≤ Pi – целое ≤ 100) и стоимость транспортировки по нему Si( 0 ≤ Si – целое ≤ 1 000 ).
Выходные данные Выходные данные заносятся в файл output.txt. · Первая строка файла содержит максимальное количество нефти, которое будет перекачано из пункта A в пункт B с минимальными затратами на транзит. · Вторая строка – затраты на транзит.
Пример
input.txt.
output.txt
В потоке минимальной стоимости вы как раз и платите cd. Потоки не решают в общем случае задачу, когда нужно 1 раз заплатить с. Попробуйте сдать задачу I в той же тренировке Миланина, убедитесь. Рекомендую почитать нормальную литературу по потокам минимальной стоимости, у Седжвика, например.
Первый пункт не создает циклы отрицательной длины, потому что обратные ребра в начале не будут участвовать в поиске кратчайшего пути (они ведь "насыщены", у них flow = capacity = 0).
Спасибо за задачу H из контеста Миланина. Мой алгоритм не прошёл даже тест из условия.
Ваша матрица смежности int g[n][n] не помогает, я вчера проверил. Ведь этот алгоритм создаёт мультиграф, т.е. между двумя вершинами образуется 4 ребра, т.к. граф неориентированный.
Да, я не совсем верно написал. Вы храните список, в котором лежат ВСЕ ребра (например, назовем e), а также списки смежности g. Только в g[i] теперь у вас будут храниться индексы ребер в e, выходящие из i-ой вершины. Например, есть неориентированное ребро 1-2, допустим в e они все лежат на 0-3. Тогда в g[1] вы храните список {0,3}. Первый индекс соответствует прямому ребру из 1 в 2, а второй обратному ребру 2->1. Почему это удобно? Потому что теперь, когда вы проталкиваете поток по ребру с индексом id, чтобы получить обратное к нему, достаточно сделать id^1.
Спасибо, хорошая идея, после сегоднешнего раунда обязательно попробую.
priority_queue не хочет компилироваться, т.к. в куче два параметра. Увы, тест
Убивает все ваши советы, единственное, что я не попробовал, это удаление циклов отрицательной длины.
1) Сделать priority_queue от pair<int, int> и в первом элементе пары хранить вес, а во второй — номер вершины. И так как priority_queue по умолчанию располагает элементы с большим приоритетом раньше, то необходимо поменять поведение используя greater:
2) Использовать структуру в котором будет храниться вес и номер вершины с переопределенным оператором <:
Вместо пропускной способности и количества пропущенного потока (
flow
иrflow
) можно просто использовать остаточную пропускную способностьremFlow
равнуюflow - rflow
. Т.е. изначально оно будет равно пропускной способности, а затем, вместоrflow += addFlow
делатьremFlow -= addFlow
и для обратного ребра наоборот. А вместоrflow < flow
:remFlow > 0
.Сегодня пробовал. Столкнулся с бедой как определить стоит ли нам снова заплатить за ребро или же наоборот. Может стоит хранить отдельный массив пропускных способностей рёбер (я так проблему решил) или есть способ попроще?
И самый главный вопрос по алгоритму. Как всё-таки расставлять потенциалы? Ведь первый пункт алгоритма порождает циклы отрицательного веса. И вчера и сегодня я пробовал использовать алгоритм Форда-Беллмана и естественно он зациклился, тогда я ставил ограничитель итераций n-1 и получил неправильный ответ. Заработает ли вообще данный алгоритм, если ещё добавить удаление циклов отрицательного веса или не стоит тратить время и алгоритм обречён на гибель!?
Не могли бы вы в точности описать решаемую вами задачу? Ибо не совсем понятно, что вы имеете ввиду здесь:
Потому мы потоке платим за каждую единицу потока пропущенную через ребро. Если рассматривать задачу, что при любом количестве потока пропущенного через ребро платить одно и тоже количество, то вам стоит обратить внимание на комментарий выше.
Если изначально нет весов отрицательного веса, то можно положить их равными нулю, иначе можно заполнить данными о кратчайших расстояниях из Форда-Беллмана. А так после каждой итерации алгоритма обновлять так:
Про потенциалы можно почитать в Кормене, там где описан Алгоритм Джонсона. Ну и по потокам вам видимо еще что-нибудь нужно почитать. Из статей есть на wiki итмо и на topcoder'е.
Ваш метод расстановки потенциалов улучшил ситуацию, но всё равно алгоритм не работает. Вот код с вашими потенциалами
Если граф сделать ориентированным то прога выдаёт 5 14. Если граф сделать неориентированным прога даёт 5 17. Хотя для обоих случаев ответ 5 11.
Может добавить удаление циклов отрицательной длины, или алгоритм всё равно не заработает?
Насчёт реализации. Ошибку стоит искать в Дейкстре или я неправильно использую Беллмана-Форда?
Извините конечно, но что за бред? Запускать Форда-Беллмана до инициализации графа? ШТО?
О да, точно. Совсем с ума сошёл. Уже выдаёт 5 15 и 5 13 соответственно.
Ошибку стоит искать в вашем понимании min cost max flow, потому что определенно — оно у вас не наблюдается. Как рекоммендовали выше — почитайте что-нибудь на эту тему еще.
Вы видимо не очень понимаете, что такое прямое, что такое обратное ребро.
g[p[x]][i].flow > g[p[x]][i].rflow
— так правильнее, но и то не совсем, потому что у нас может быть несколько ребер (u, v) и в Дейкстре нужно запоминать — какое именно это ребро. Общая практика: делать список ребер:std::vector< edge_t > edges;
, а в графеg
поg[u]
хранить вектор номеров ребер исходящих в массивеedges
и в Дейкстре вp[u]
сохранить id ребра — тогда точно будете знать по какому ребру поток проходил и не надо будет его заново искать. Так же если последовательно добавлять и прямое и обратное ребро, то если поток прошел по ребруi
, то обратное к нему ребро имеет номерi ^ 1
.Тут нужно:
mincost += g[p[x]][i].cost * addflow
— потому что в min cost max flow мы платим за каждую единицу потока.Тут не нужно никакого
g[x][j].flow == 0
— лучше переделать на список ребер + списки смежности, как было описано выше.Спасибо за очень подробные объяснения. Я переписал код почти заново. Теперь выдаёт 5 11 — т.е. правильный ответ на мой тест. Причём он работает и Беллманом-Форда и без него. Сейчас попробую решить задачу H, которую посоветовал Perlik.
Upd: Без Беллмана-Форда Accpeted. С Беллманом-Форда WA4.