@apodavalov

Минимизация фактов выплат/перевозок?

Доброе время суток!


Сразу к делу.
Задача: Некоторая группа людей в количестве N человек, где каждый в разное время занимал у кого-то из этой же группы некоторую сумму денег. Необходимо выяснить каким минимальным количеством фактов выплат можно обойтись, чтобы раздать все долги. На входе подаются транзакции задолжностей. Например:


Вход:

7

D A 2

A D 10

A E 7

A F 6

B F 1

B G 5

C G 2


Выход:

5

A E 7

A F 7

A G 7

B D 2

C D 6


На данный момент я делаю следующее.

1) Ввожу понятие баланса. Изначально у всех баланс нулевой. При считывании очередной транзакции баланс того, кто должен уменьшается, баланс того, кому должны — увеличивается (оба на сумму задолжности).

2) После чтения всех транзакций всех людей можно разделить на две группы: с положительным и отрицательным балансами. С нулевым балансом можно просто отбросить. С отрицательным балансом — те, кто должен (поставщики), с положительным — те, кому должны (потребители). Сумма всех значений всегда равна нулю (т.к. количество отданных денег равна количеству полученных). После разделения все значения делаю положительными.

3) Строится транспортная сеть: добавляется исток и соединяется ребрами с каждым поставщиком, максимальная пропускная способность каждого ребра равна значению отдачи, тоже самое делается с потребителями: от них строятся ребра к добавленному стоку. Максимальная пропускная способность каждого такого ребра: значение получения. Между каждой парой поставщика и потребителя строится ребро с бесконечной пропускной способоностью (или с общей суммой отдачи/получения).

4) запускается алгоритм Форда-Фалкерсона — получаем некоторый базис. Он в общем случае еще не оптимален.

На данном этапе я застрял. Дальше не знаю что делать (все варианты которые я перепробовал — не работают, если необходимо — могу расписать что делал).

Вопрос: как минизировать количество выплат? Спасибо!


P.S. Не стал писать математическую формализацию задачи в виде системы линейных уравнений и функции, которую надо минимизировать. Если надо — сообщите об этом в комментариях.
  • Вопрос задан
  • 3213 просмотров
Решения вопроса 1
@apodavalov Автор вопроса
Добрый день!


Извиняюсь, что долго не отвечал. Слег с болезнью под новый год. Потом что-то руки не доходили. Сейчас дошли.

Во-первых, спасибо большое!

Во-вторых, портировал на C#. Я напишу тут несколько замечаний, чтобы исправить ошибки, которые (возможно) есть в коде.

equalizer.cpp


Строка 268:

for (int i = 0; i < sorted_balance.length() — 1; i++)

Почему "- 1"?

Строка 87:

Строка 239:

if (temp.isEmpty() || temp_list.length() + 1 < temp.length())

Почему "+ 1"?

Строка 119:

Вот это:

if (!used.contains(first_key))

{

for (int j = i + 1; j < balance.keys().length(); j++)

Нужно сделать так:

for (int j = i + 1; j < balance.keys().length() && !used.contains(first_key); j++)

Так как first_key может попасть в массив used внутри цикла по j


В третьих, по поводу кода: пока среди моих тестов проходят все. Подготовлю тесты побольше. И буду вкуривать теперь логику работы. Еще раз спасибо =)

Добавлено спустя два года:
Для того, чтобы решить задачу достаточно найти все минимальные подгруппы в которых сумма балансов равна 0 (в таких группах не содержатся другие подгруппы сумма балансов которых равна 0). Затем просто раздать балансы внутри полученных подгрупп "в лоб".
Для решения первой подзадачи можно использовать: https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D...
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
@mithraen
Вы усложнили себе задачу.

В этой задаче цепочки передачи денег не имеют смысла — они не уменьшают количество транзакций. Скажем у A и B баланс отрицательный, у C — положительный. A может передать деньги B, чтобы он передал C все одной транзакций, а могут передавать их каждый сам напрямую C, от этого ничего не изменится.

Поэтому мы можем считать что нет никакой сети, у нас есть отправители денег (те у кого отрицательный баланс) и получатели (те, у кого он положительный). Каждый отправитель должен послать деньги одному или нескольким получателям.

Я бы предложил простой алгоритм, хотя доказать его оптимальность я не могу:

1. Ищем есть ли у нас пары получатель/отправитель с одинаковым по модулю балансу. Если есть — прекрасно, делаем платежку и обнуляем их балансы (забываем про них).
2. Находим того, кто должен больше всех (отправитель)
3. Находим того, кому должны наибольшую сумму (получатель)
4. Делаем платежку на сумму, наименьшую из этих двух (если у нас есть отправитель который должен суммарно 20, а самый крупный получатель должен получить 11 — делаем платежку от отправителя к получателю на 11)
5. Повторяем, пока не останется ни одной задолженности
Ответ написан
alan_mun
@alan_mun
Приветствую!

Попробовал решить Вашу задачку, и вот результат в виде кода на Qt.

Предлагаю решать её так:
1. С полной уверенностью находим баланс для транзакций.
2. Удаляем все совпадения из баланса (записывая транзакцию в ответ) и все объекты с нулевым балансом.
3. Находим самый маленький по модулю баланс.
4. А сейчас начинается милая сердцу рекурсия: для каждого из баланса другого знака создаём и проводим транзакцию. И запускаем эту же функцию с новыми значениями баланса.
5. Если получившийся результат в пункте 4 самый короткий по числу транзакций — радостно сохраняем его и передаём наверх.

Естественно пугаюсь того что рекурсия будет очень прожорливой.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы