Справедливый алгоритм распределения товаров - PullRequest
5 голосов
/ 09 ноября 2010

Вот моя проблема:

  • Есть n компаний, распространяющих товары.
  • Все продукты должны быть распределены в k дней
  • Распространение продукции компании Ci должно быть последовательным - это означает, что она может распространяться в дни 2,3,4,5, но не 2,3,6,7
  • количество распределенных продуктов компанией Ci в день j должно быть меньше (или равно) в день j-1 (если их было в день j-1)
  • разница между распределенными продуктами между днями i и j не должна превышать 1

Пример:

У нас есть 3 дня, чтобы распространять продукты. Продукция компании А: а, а, а, а, а. Продукция компании B: B, B, B. Продукция компании C: C, C

Справедливое распределение: [AAB, AABC, ABC] * +1021 *

Неверное распространение: [AABC, AABC, AB] потому что в 1-й день 4 продукта, в 3-й день 2 продукта (разница> 1)

Неверное распространение: [ABC, AABC, AAB] потому что в 1-й день есть один продукт A, а во 2-й день есть 2 продукта A, поэтому распределение продукта A не уменьшается

EDIT если есть случай, который делает справедливое распространение невозможным, предоставьте его краткое описание, я приму ответ

Ответы [ 4 ]

2 голосов
/ 10 ноября 2010

Так как я думал, что проблема была забавной, я сделал модель для поиска решений, используя MiniZinc . С бэкэндом Gecode в первоначальном примере показано 20 решений за 1,6 мс.

include "globals.mzn";

%%% Data
% Number of companies
int: n = 3;
% Number of products per company
array[1..n] of int: np = [5, 3, 2];
% Number of days
int: k = 3;

%%% Computed values
% Total number of products
int: totalnp = sum(np);
% Offsets into products array to get single companys products 
% (shifted cumulative sum).
array[1..n] of int: offset = [sum([np[j] | j in 1..i-1]) 
                          | i in 1..n];

%%% Predicates
predicate fair(array[int] of var int: x) =
      let { var int: low,
            var int: high
      } in
        minimum(low, x) /\
        maximum(high, x) /\
        high-low <= 1;
predicate decreasing_except_0(array[int] of var int: x) =
        forall(i in 1..length(x)-1) (
                 (x[i] == 0) \/
                 (x[i] >= x[i+1])
        );
predicate consecutive(array[int] of var int: x) =
        forall(i in 1..length(x)-1) (
             (x[i] == x[i+1]) \/
             (x[i] == x[i+1]-1)
        );

%%% Variables
% Day of production for all products from all companies
array[1..totalnp] of var 1..k: products 
          :: is_output;
% total number of products per day
array[1..k] of var 1..totalnp: productsperday 
        :: is_output;

%%% Constraints 
constraint global_cardinality(products, productsperday);
constraint fair(productsperday);
constraint
    forall(i in 1..n) (
         let { 
             % Products produced by company i
             array[1..np[i]] of var int: pi
                = [products[j] | 
                 j in 1+offset[i]..1+offset[i]+np[i]-1],
             % Products per day by company i
             array[1..k] of var 0..np[i]: ppdi
         } in
           consecutive(pi) /\
           global_cardinality(pi, ppdi) /\
           decreasing_except_0(ppdi)
    );

%%% Find a solution, default search strategy
solve satisfy;

Предикаты decreasing_except_0 и consecutive оба очень наивны и имеют большие разложения. Для решения более крупных случаев, вероятно, следует заменить их на более умные варианты (например, с помощью регулярного ограничения).

2 голосов
/ 09 ноября 2010

Комментарий Гарета Риса к ответу djna верен - следующий контрпример неразрешимый :

  • 3 дня, 7 товаров от компании A и 5 товаров от компании B

Я проверил это с помощью следующей самой тупой Perl-программы с грубой силой (которая занимает не более секунды, несмотря на свою неэффективность):

my ($na, $nb) = (7, 5);
for (my $a1 = 0; $a1 <= $na; ++$a1) {
    for (my $a2 = 0; $a2 <= $na - $a1; ++$a2) {
        my $a3 = $na - $a1 - $a2;
        for (my $b1 = 0; $b1 <= $nb; ++$b1) {
            for (my $b2 = 0; $b2 <= $nb - $b1; ++$b2) {
                my $b3 = $nb - $b1 - $b2;
                if ($a1 >= $a2 && $a2 >= $a3 || $a1 == 0 && $a2 >= $a3 || $a1 == 0 && $a2 == 0) {
                    if ($b1 >= $b2 && $b2 >= $b3 || $b1 == 0 && $b2 >= $b3 || $b1 == 0 && $b2 == 0) {
                        if (max($a1 + $b1, $a2 + $b2, $a3 + $b3) - min($a1 + $b1, $a2 + $b2, $a3 + $b3) <= 1) {
                            print "Success! ($a1,$a2,$a3), ($b1,$b2,$b3)\n";
                        }
                    }
                }
            }
        }
    }
}

Пожалуйста, посмотрите и убедитесь, что я не допустил глупых ошибок. (Я опускаю max() и min() для краткости - они просто делают то, что вы ожидаете.)

1 голос
/ 09 ноября 2010

Было показано, что точки 4 и 5 несовместимы:

  • 4: для любого дня j, для любой компании A, C (j, A) == 0 или C (j, A)> = C (j + 1, A)
  • 5: Для любых дней i и j, |C(i) - C(j)| <= 1

Таким образом, вам нужно ослабить любое ограничение.Честно говоря, хотя я чувствую, почему было введено 4 (чтобы не задерживать распространение одной компании на неопределенный срок), я думаю, что можно было бы выразить иначе считать первый и последний день распространения как особые (поскольку наВ первый день вы обычно берете то, что осталось от предыдущей компании, и в последний день вы распределяете то, что осталось).

Точка 3 налагает смежность.У любой компании А, которая имеет продукцию, существует два дня i и j, такие что:

  • C (i, A)> 0 и C (j, A)> 0
  • для любого дня x, такого что x j, C (x, A) = 0
  • для любого дня x такого, что i

По общему признанию, тогда проблема становится тривиальной для решения:)

0 голосов
/ 09 ноября 2010

Не думаю, что вы всегда сможете выполнить ваши требования.

Рассмотрим 4 дня и 6 товаров от поставщика A и 6 товаров от поставщика B.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...