Вырезать прямоугольник в минимальном количестве квадратов
Я пытаюсь решить следующую проблему:
Прямоугольный лист бумаги 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)