C # 0-1 Задача о ранце с известной суммой и количеством нулей в наборе

У меня есть таблица значений 5x5 от 0 до 3 включительно со всеми неизвестными значениями. Я знаю как сумму значений, так и количество нулей для каждой строки и столбца. Как я могу решить эту задачу с рюкзаком 0-1 с помощью C # и получить возможные решения, которые удовлетворяют известным суммам и количеству нулей? В таблицах всегда будет пять строк и пять столбцов, так что это не совсем традиционный рюкзак.

Например, скажем, мы вводим:

Row[0]: Sum=4, Zeros=1
   [1]: Sum=5, Zeros=1
   [2]: Sum=4, Zeros=2
   [3]: Sum=8, Zeros=0
   [4]: Sum=3, Zeros=2

Col[0]: Sum=5, Zeros=1
   [1]: Sum=3, Zeros=2
   [2]: Sum=4, Zeros=2
   [3]: Sum=5, Zeros=1
   [4]: Sum=7, Zeros=0

Мы бы получили это как возможное решение:

[[ 0 1 1 1 1 ]
 [ 1 0 2 1 1 ]
 [ 2 1 0 0 1 ]
 [ 1 1 1 2 3 ]
 [ 1 0 0 1 1 ]]

Какой алгоритм мне следует использовать в этой довольно странной ситуации? Придется ли мне также написать класс только для перечисления перестановок?

Отредактируйте для пояснения: проблема не в том, что я не могу перечислить возможности; Дело в том, что я понятия не имею, как эффективно определять перестановки, добавляемые к произвольной сумме, при этом они содержат указанное количество нулей и максимум 5 элементов.


person hydroiodic    schedule 04.09.2011    source источник
comment
Какой код вы писали до сих пор для решения этой проблемы? Вы можете опубликовать это?   -  person Caspar Kleijne    schedule 04.09.2011
comment
и, возможно, добавить тег домашнего задания (если это не домашнее задание, мне бы очень хотелось знать, что это такое).   -  person Valmond    schedule 04.09.2011
comment
Как это связано с рюкзаком?   -  person flight    schedule 04.09.2011
comment
Технически можно просто перечислить все решения. Есть 2 ^ 50 возможных таблиц, затем вы проверяете свои ограничения.   -  person xanatos    schedule 04.09.2011
comment
@Valmond: Не совсем домашнее задание, просто упражнение, чтобы облегчить мое разочарование от игры-головоломки.   -  person hydroiodic    schedule 04.09.2011
comment
@CKoenig Я считаю, что вы имеете в виду NP. Рюкзак - задача, решаемая динамическим программированием.   -  person flight    schedule 04.09.2011
comment
Что сказал квазивселенная. Я также считаю, что это не рюкзак, а проблема NP (трудная?). Однако брутфорс должен быстро найти решение.   -  person evgeny    schedule 04.09.2011
comment
да извините - я подумал о люке ... извините   -  person Random Dev    schedule 04.09.2011


Ответы (2)


Вот код. Если вам нужен комментарий, не стесняйтесь спрашивать:

using System;
using System.Diagnostics;

namespace ConsoleApplication15
{
    class Program
    {
        static void Main(string[] args)
        {
            RowOrCol[] rows = new RowOrCol[] { 
                new RowOrCol(4, 1),
                new RowOrCol(5, 1),
                new RowOrCol(4, 2),
                new RowOrCol(8, 0),
                new RowOrCol(3, 2),
            };

            RowOrCol[] cols = new RowOrCol[] { 
                new RowOrCol(5, 1),
                new RowOrCol(3, 2),
                new RowOrCol(4, 2),
                new RowOrCol(5, 1),
                new RowOrCol(7, 0),
            };

            int[,] table = new int[5, 5];

            Stopwatch sw = Stopwatch.StartNew();

            int solutions = Do(table, rows, cols, 0, 0);

            sw.Stop();

            Console.WriteLine();
            Console.WriteLine("Found {0} solutions in {1}ms", solutions, sw.ElapsedMilliseconds);
            Console.ReadKey();
        }

        public static int Do(int[,] table, RowOrCol[] rows, RowOrCol[] cols, int row, int col)
        {
            int solutions = 0;

            int oldValueRowSum = rows[row].Sum;
            int oldValueRowZero = rows[row].Zeros;
            int oldValueColSum = cols[col].Sum;
            int oldValueColZero = cols[col].Zeros;

            int nextCol = col + 1;
            int nextRow;
            bool last = false;

            if (nextCol == cols.Length)
            {
                nextCol = 0;

                nextRow = row + 1;

                if (nextRow == rows.Length)
                {
                    last = true;
                }
            }
            else
            {
                nextRow = row;
            }

            int i;

            for (i = 0; i <= 3; i++)
            {
                table[row, col] = i;

                if (i == 0)
                {
                    rows[row].Zeros--;
                    cols[col].Zeros--;

                    if (rows[row].Zeros < 0)
                    {
                        continue;
                    }

                    if (cols[col].Zeros < 0)
                    {
                        continue;
                    }
                }
                else
                {
                    if (i == 1)
                    {
                        rows[row].Zeros++;
                        cols[col].Zeros++;
                    }

                    rows[row].Sum--;
                    cols[col].Sum--;

                    if (rows[row].Sum < 0)
                    {
                        break;
                    }
                    else if (cols[col].Sum < 0)
                    {
                        break;
                    }
                }

                if (col == cols.Length - 1)
                {
                    if (rows[row].Sum != 0 || rows[row].Zeros != 0)
                    {
                        continue;
                    }
                }

                if (row == rows.Length - 1)
                {
                    if (cols[col].Sum != 0 || cols[col].Zeros != 0)
                    {
                        continue;
                    }
                }

                if (!last)
                {
                    solutions += Do(table, rows, cols, nextRow, nextCol);
                }
                else 
                {
                    solutions++;

                    Console.WriteLine("Found solution:");

                    var sums = new int[cols.Length];
                    var zeross = new int[cols.Length];

                    for (int j = 0; j < rows.Length; j++)
                    {
                        int sum = 0;
                        int zeros = 0;

                        for (int k = 0; k < cols.Length; k++)
                        {
                            Console.Write("{0,2} ", table[j, k]);

                            if (table[j, k] == 0)
                            {
                                zeros++;
                                zeross[k]++;
                            }
                            else
                            {
                                sum += table[j, k];
                                sums[k] += table[j, k];
                            }
                        }

                        Console.WriteLine("| Sum {0,2} | Zeros {1}", sum, zeros);

                        Debug.Assert(sum == rows[j].OriginalSum);
                        Debug.Assert(zeros == rows[j].OriginalZeros);
                    }

                    Console.WriteLine("---------------");

                    for (int j = 0; j < cols.Length; j++)
                    {
                        Console.Write("{0,2} ", sums[j]);
                        Debug.Assert(sums[j] == cols[j].OriginalSum);
                    }

                    Console.WriteLine();

                    for (int j = 0; j < cols.Length; j++)
                    {
                        Console.Write("{0,2} ", zeross[j]);
                        Debug.Assert(zeross[j] == cols[j].OriginalZeros);
                    }

                    Console.WriteLine();
                }
            }

            // The for cycle was broken at 0. We have to "readjust" the zeros.
            if (i == 0)
            {
                rows[row].Zeros++;
                cols[col].Zeros++;
            }

            // The for cycle exited "normally". i is too much big because the true last cycle was at 3.
            if (i == 4)
            {
                i = 3;
            }

            // We readjust the sums.
            rows[row].Sum += i;
            cols[col].Sum += i;

            Debug.Assert(oldValueRowSum == rows[row].Sum);
            Debug.Assert(oldValueRowZero == rows[row].Zeros);
            Debug.Assert(oldValueColSum == cols[col].Sum);
            Debug.Assert(oldValueColZero == cols[col].Zeros);

            return solutions;
        }
    }

    public class RowOrCol
    {
        public readonly int OriginalSum;
        public readonly int OriginalZeros;

        public int Sum;
        public int Zeros;

        public RowOrCol(int sum, int zeros)
        {
            this.Sum = this.OriginalSum = sum;
            this.Zeros = this.OriginalZeros = zeros;
        }
    }
}
person xanatos    schedule 04.09.2011
comment
Помечено как ответ и +1. Спасибо за помощь! Меня поразила рекурсия. - person hydroiodic; 04.09.2011
comment
@hydroiodic Наверное, это можно сделать без рекурсии и стеков, понимаете? - person xanatos; 04.09.2011

Как быстро это должно быть? Я только что проверил наивное «пробовать что угодно» с некоторыми преждевременными прерываниями, но меньше, чем было бы возможно, и это было довольно быстро (менее миллисекунды). Это дало решение:

[[ 0 1 1 1 1 ]
 [ 1 0 1 1 2 ]
 [ 1 0 0 1 2 ]
 [ 2 1 2 2 1 ]
 [ 1 1 0 0 1 ]]

Если это приемлемое решение для вас, я могу опубликовать код (или просто обсудить его, это довольно многословно, но основная идея тривиальна)

edit: его также можно тривиально расширить до перечисления всех решений. Он обнаружил 400 из них за 15 миллисекунд и утверждает, что их не больше. Это верно?


Я начал с 0,0 и попробовал все значения, которые я мог заполнить в этом месте (0, хотя min (3, rowsum [0])), заполнить его (вычтя из rowsum [y] и colsum [x ] и вычитая единицу из rowzero [y] и colzero [x], если значение было нулем), затем рекурсивно сделайте это для 0,1; 0,2; 0,3; тогда при 0,4 у меня есть особый случай, когда я просто заполняю оставшуюся сумму строк, если она неотрицательна (в противном случае, прерываю текущую попытку - т.е. поднимаюсь в дереве рекурсии), и что-то подобное, когда y = 4. В то же время я прерываю выполнение, когда любая строка rowzero colsum colzero или rowzero становится отрицательной.

Текущая плата является решением тогда и только тогда, когда все оставшиеся строки, суммы, столбцы, colzero и rowzero, равны нулю. Поэтому я просто проверяю это и добавляю к решениям, если это так. По конструкции у него не будет отрицательных записей.

person harold    schedule 04.09.2011
comment
Хорошо, добавил объяснение того, что я сделал. Вам тоже нужен код? - person harold; 04.09.2011
comment
+1. Это снова поставило меня на ноги. Спасибо! Я делал кучу уродливых циклов for внутри циклов for внутри циклов foreach, прежде чем я увидел этот ответ. - person hydroiodic; 04.09.2011
comment
Я просто попытался ограничить выбор для каждой записи больше (с помощью таблицы поиска, которая принимает количество нулей и сумму), и, хотя это отбросило примерно половину вариантов, оно оказалось медленнее. Проблемный экземпляр действительно слишком мал, чтобы такие уловки окупились :( - person harold; 04.09.2011