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

Я пытаюсь решить следующую проблему:

Прямоугольный лист бумаги M * N должен быть разрезан на квадраты так, что:

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

Процесс останавливается, когда бумага не может быть разрезана.

Каково минимальное количество разрезанных кусков бумаги, чтобы все были квадратами?

Пределы: 1 <= N <= 100 и 1 <= M <= 100.

Пример: пусть N = 1 и M = 2, тогда ответ равен 2, так как минимальное количество квадратов, которые можно разрезать, равно 2 (бумага разрезается горизонтально вдоль меньшей стороны посередине).

Мой код:

cin >> n >> m;

int N = min(n,m);
int M = max(n,m);
int ans = 0;

while (N != M) {
    ans++;
    int x = M - N;
    int y = N;
    M = max(x, y);
    N = min(x, y);
}

if (N == M && M != 0)
   ans++;

Но я не понимаю, что неправильно с этим подходом, поскольку это дает мне неправильный ответ.

Ответы

Ответ 1

Я бы написал это как динамическую (рекурсивную) программу.

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

Основной случай был бы равен, если обе стороны равны, т.е. вход уже является квадратом, и в этом случае результат равен 1.

function min_squares(m, n):

    // base case:
    if m == n: return 1

    // minimum number of squares if you split vertically:
    min_ver := min { min_squares(m, i) + min_squares(m, n-i)  |  i ∈ [1, n/2] }

    // minimum number of squares if you split horizontally:
    min_hor := min { min_squares(i, n) + min_squares(m-i, n)  |  i ∈ [1, m/2] }

    return min { min_hor, min_ver }

Чтобы повысить производительность, вы можете кэшировать рекурсивные результаты:

function min_squares(m, n):

    // base case:
    if m == n: return 1

    // check if we already cached this
    if cache contains (m, n):
        return cache(m, n)

    // minimum number of squares if you split vertically:
    min_ver := min { min_squares(m, i) + min_squares(m, n-i)  |  i ∈ [1, n/2] }

    // minimum number of squares if you split horizontally:
    min_hor := min { min_squares(i, n) + min_squares(m-i, n)  |  i ∈ [1, m/2] }

    // put in cache and return
    result := min { min_hor, min_ver }
    cache(m, n) := result
    return result

В конкретной реализации С++ вы можете использовать int cache[100][100] для структуры данных кэша, так как ваш размер ввода ограничен. Поместите его как статическую локальную переменную, поэтому она будет автоматически инициализирована нулями. Затем интерпретируйте 0 как "не кэшированный" (поскольку он не может быть результатом каких-либо входов).

Возможная реализация на С++: http://ideone.com/HbiFOH

Ответ 2

Жадный алгоритм не является оптимальным. На прямоугольнике 6x5 он использует квадраты 5x5 и 5 1x1. Оптимальное решение использует 2 3x3 квадрата и 3 2x2 квадрата.

Чтобы получить оптимальное решение, используйте динамическое программирование. Рекурсивное решение грубой силы пробует все возможные горизонтальные и вертикальные разрезы, рекурсивно разрезая две части оптимально. Кэшируя (memoizing) значение функции для каждого входа, мы получаем динамическую программу с полиномиальным временем (O (m n max (m, n))).

Ответ 3

Я думаю, что и DP, и жадные решения не оптимальны. Вот контрпример для решения DP:

Рассмотрим прямоугольник размера 13 X 11. Решение DP дает 8 в качестве ответа. Но оптимальное решение имеет только 6 квадратов.

введите описание изображения здесь

В этом потоке есть много примеров-счетчиков: https://mathoverflow.net/questions/116382/tiling-a-rectangle-with-the-smallest-number-of-squares

Кроме того, посмотрите на это для правильного решения: http://int-e.eu/~bf3/squares/

Ответ 4

Эта проблема может быть решена с помощью динамического программирования.

Предполагая, что у нас прямоугольник с шириной равен N, а высота равна M.

  • if (N == M), поэтому это квадрат, и ничего не нужно делать.

  • В противном случае мы можем разделить прямоугольник на два других меньших (N - x, M) и (x, M), поэтому его можно решить рекурсивно.

  • Аналогично, мы можем также разделить его на (N, M - x) и (N, x)

Псевдокод:

int[][]dp;
boolean[][]check;
int cutNeeded(int n, int m)

    if(n == m)
       return 1;
    if(check[n][m])
       return dp[n][m];
    check[n][m] = true;
    int result = n*m;
    for(int i = 1; i <= n/2; i++)
       int tmp = cutNeeded(n - i, m) + cutNeeded(i,m);
       result = min(tmp, result); 

    for(int i = 1; i <= m/2; i++)
       int tmp = cutNeeded(n , m - i) + cutNeeded(n,i);
       result = min(tmp, result); 
    return dp[n][m] = result;

Ответ 5

Это по существу классическая целая или 0-1 проблема ранца, которая может быть решена с использованием жадного или динамического подхода к программированию. Вы можете ссылаться на: Решение целочисленного рюкзака

Ответ 6

Вот жадный имп. Поскольку @David упомянул, что это не оптимально и совершенно неверно, в некоторых случаях такой динамический подход является лучшим (с кешированием).

def greedy(m, n):
    if m == n:
        return 1
    if m < n:
        m, n = n, m
    cuts = 0

    while n:
        cuts += m/n
        m, n = n, m % n
    return cuts

print greedy(2, 7)

Вот попытка DP в python import sys

def cache(f):
    db = {}

    def wrap(*args):
        key = str(args)
        if key not in db:
            db[key] = f(*args)
        return db[key]
    return wrap


@cache
def squares(m, n):
    if m == n:
        return 1
    xcuts = sys.maxint
    ycuts = sys.maxint
    x, y = 1, 1
    while x * 2 <= n:
        xcuts = min(xcuts, squares(m, x) + squares(m, n - x))
        x += 1
    while y * 2 <= m:
        ycuts = min(ycuts, squares(y, n) + squares(m - y, n))
        y += 1
    return min(xcuts, ycuts)