Блог пользователя Izot_NNSTU

Автор Izot_NNSTU, 10 лет назад, По-русски

Столкнулся с задачкой, которую легко переформулировать в терминах олимпиадного программирования. Уверен, такое решалось 100000 раз, но я что-то не придумал быстро элегантного решения (теряются навыки ACM) :)

n друзей посидели в кафе и расплатились не оставив ни копейки чаевых. Кто-то заплатил больше, чем он должен, кто-то меньше. Теперь друзьям надо рассчитаться друг с другом, при этом передавая деньги из рук в руки наименьшее число раз.

Формально так: есть n целых чисел (балансы друзей), сумма этих чисел равна 0. За ход можно взять пару чисел и прибавить произвольное целое число к первому числу этой пары, при этом отняв от второго.

n1, n2 -> n1'=n1+k, n2'=n2-k

Задача — за наименьшее число ходов превратить все числа в 0.

Не смог убедить себя, что жадный алгоритм работает :)

Единственное, что придумал — пролить макспоток минимальной стоимости через граф с истоком, стоком, слоем должников и слоем кредиторов, где каждый должник соединен с каждым кредитором ребром с пропускной способностью бесконечность. При этом функция стоимости будет странная — 1 если по ребру течет какой-то поток и 0 в противном случае. Вообще не уверен, что алгоритм нахождения потока будет работать с такой функцией.

Короче говоря, я уверен, что это какая-то стандартная задача. Кто напомнит как такое решать?

Кстати, задача становится немного интереснее, если добавить ограничение на номинал купюр, которые есть у друзей.

  • Проголосовать: нравится
  • +8
  • Проголосовать: не нравится

»
10 лет назад, # |
Rev. 2   Проголосовать: нравится +8 Проголосовать: не нравится

Пусть есть много(n) положительных чисел и два отрицательных вида -SUM/2

Тогда проверить возможность оплаты в n транзакций — вариация задачи о рюкзаке(набрать sum/2 как подмножество).

То есть при отсутствии ограничений на числа за полином от кол-ва решения не будет.

»
10 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Да, вроде верно. Я прозрел :)

Ок, как в таком случае решать это вообще, пусть даже за экспоненту? Если числа не очень большие (скажем до 1000) и n не больше 30. Нужна какая-то динамика с масками тогда.

В общем, в таком случае задача такая — придумать алгоритм решения, который будет работать для максимального n и величины чисел. Может MITM какой-нибудь запихнуть можно?

»
10 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Что нам мешает обнулять все числа в порядке уменьшения их баланса? Пусть A-минимальный баланс, B-максимальный, тогда делаем присваивание A -  = K и B +  = K, где K = min(A,  - B). Если если число A или B становится 0, то просто говорим говорим, что дальше мы его рассматривать не будем, т.к. для него задача уже решена.
Поправьте меня, если я не прав.

  • »
    »
    10 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Это не оптимально?

    Пусть есть числа 3 3 2 2 2 -6 -6. Все укладывается в 5 операций, у вас тройки пойдут в разные 6-ки и дальше уже 3х операций не хватит

    • »
      »
      »
      10 лет назад, # ^ |
      Rev. 4   Проголосовать: нравится 0 Проголосовать: не нравится

      Да, действительно. А если это же максимальное на данный момент число A ложить в самое большое число B, что B + A <  = 0. Тогда вроде на каждом шаге мы обнуляем как минимум один элемент. Или это тоже неверно?)
      upd. Если такого числа B не нашлось, то множим все числа на -1 и пробуем еще раз.
      upd. Понял, что тоже неверно, но контр-пример так и не придумал.

  • »
    »
    10 лет назад, # ^ |
    Rev. 3   Проголосовать: нравится 0 Проголосовать: не нравится

    Возьмем числа (10, 9) и (-8, -5, -5, -1)

    По вашему алгоритму получится такое решение:

    1) (2, 9) — (0, -5, -5, -1)

    2) (2, 4) — (0, 0, -5, -1)

    3) (2, 0) — (0, 0, -1, -1)

    4) (1, 0) — (0, 0, 0, -1)

    5) (0, 0) — (0, 0, 0, 0)

    Хотя можно оптимальнее:

    1) (5, 9) — (-8, 0, -5, -1)

    2) (0, 9) — (-8, 0, 0, -1)

    3) (0, 1) — (0, 0, 0, -1)

    4) (0, 0) — (0, 0, 0, 0)

    Опять протормозил, что-то сервер CF тупит сегодня

»
10 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Тут скорее не экспоненциальное надо, а неточное искать. Понятно, что жадно можно решить на n - 1, где n — количество ненулевых чисел (вливать все время наименьший по модулю). И ясно, что меньше, чем за max(#positive, #negative) нельзя. Поэтому имеет смысл применять какую-то такую эвристику — вначале искать все пары x,  - x и исключать их, потом перебирать тройки с x + y + z = 0 исключать их и тд, а в конце жадно добивать оставшееся.

  • »
    »
    10 лет назад, # ^ |
      Проголосовать: нравится +8 Проголосовать: не нравится

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