18

I'm trying to solve the following problem:

A rectangular paper sheet of M*N is to be cut down into squares such that:

  1. The paper is cut along a line that is parallel to one of the sides of the paper.
  2. The paper is cut such that the resultant dimensions are always integers.

The process stops when the paper can't be cut any further.

What is the minimum number of paper pieces cut such that all are squares?

Limits: 1 <= N <= 100 and 1 <= M <= 100.

Example: Let N=1 and M=2, then answer is 2 as the minimum number of squares that can be cut is 2 (the paper is cut horizontally along the smaller side in the middle).

My code:

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++;

But I am not getting what's wrong with this approach as it's giving me a wrong answer.

leemes
  • 44,967
  • 21
  • 135
  • 183
user3840069
  • 765
  • 1
  • 6
  • 20

6 Answers6

21

I think both the DP and greedy solutions are not optimal. Here is the counterexample for the DP solution:

Consider the rectangle of size 13 X 11. DP solution gives 8 as the answer. But the optimal solution has only 6 squares.

enter image description here

This thread has many counter examples: https://mathoverflow.net/questions/116382/tiling-a-rectangle-with-the-smallest-number-of-squares

Also, have a look at this for correct solution: http://int-e.eu/~bf3/squares/

Will_of_fire
  • 1,089
  • 4
  • 12
  • 24
  • I don't understand why this answer has so less upvotes, DP approach doesn't work for this problem! – Manish Sharma Jan 31 '18 at 15:06
  • 3
    @ManishSharma: problem statement says, "The paper is cut along a line that is parallel to one of the sides of the paper" This answer solves some other question which is different from what have been asked. – Bharat Kul Ratan Jun 02 '20 at 09:12
  • 1
    That is true in this example image as well, isn’t it? Parallel just means it is never cut with for example 45 degree rotations (I’m having trouble imagining where that would work for a minimum) I think you are meaning to add a rule like “and the resultant shape also is always a rectangle” – roberto tomás Sep 25 '20 at 15:26
15

I'd write this as a dynamic (recursive) program.

Write a function which tries to split the rectangle at some position. Call the function recursively for both parts. Try all possible splits and take the one with the minimum result.

The base case would be when both sides are equal, i.e. the input is already a square, in which case the result is 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 }

To improve performance, you can cache the recursive results:

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

In a concrete C++ implementation, you could use int cache[100][100] for the cache data structure since your input size is limited. Put it as a static local variable, so it will automatically be initialized with zeroes. Then interpret 0 as "not cached" (as it can't be the result of any inputs).

Possible C++ implementation: http://ideone.com/HbiFOH

shubham rajput
  • 1,015
  • 1
  • 9
  • 12
leemes
  • 44,967
  • 21
  • 135
  • 183
  • @user3840069 I just edited it ;) You now only have to write those mathematically written minimum expressions as loops. Loop over i and update a variable in which you keep track of the current minimum value. – leemes Sep 18 '14 at 02:53
  • @user3840069 Now I even added a version with result caching. – leemes Sep 18 '14 at 03:00
  • @PhamTrung The funny thing is that I did that in my actual C++ implementation but when "porting back" to pseudo code I wrote it wrong :) -- Corrected, thanks. – leemes Sep 18 '14 at 03:04
  • I think one optimisation is min_squares(k*m,k*n)=min_squares(m,n) where k is some integer greater than 1. – Jason L Sep 18 '14 at 03:07
  • @user3840069 I thought I'd better leave the C++ code out since it's a coding puzzle and you should also have something left to do. But here you go: http://ideone.com/HbiFOH – leemes Sep 18 '14 at 03:15
  • +1. A small optimisation: We don't care about the orientation of the rectangle, so we can always "rotate" it if necessary so that, e.g., m < n. – j_random_hacker Sep 18 '14 at 08:51
  • Regarding JasonL's suggestion, I'm not sure it will always work, but a weaker strategy will: you can find all prime factors in gcd(m, n), and for each such factor p, try solving min_squares(m/p, n/p). After that you only need to solve the subproblems not already covered -- that is, the ones that produce at least one square having relatively prime side lengths. Doing all this might be slower overall, though! – j_random_hacker Sep 18 '14 at 09:06
  • 1
    @j_random_hacker Regarding your "swap so that it's ordered"-suggestion: it's implemented in the C++ solution. – leemes Sep 18 '14 at 21:28
  • 1
    @MehmetFide For this, the function should return not only the count but also the list of widths (in C++ probably as a vector; it automatically carries the number of squares then as `vector.size()`). The minimum function should then compare the sizes and return the smaller vector (you can use `std::min` for this by providing an appropriate compare function like `[](const vector &a, const vector &b){ return a.size() < b.size(); }`. The base case becomes `return { n };` i.e. a single-element vector with the single square's width. – leemes Oct 08 '14 at 19:49
  • @leemes just FYI: above algorithm doesn't yield the optimum solution. – Mehmet Fide Dec 23 '14 at 11:46
  • @MehmetFide Why is that? – leemes Dec 23 '14 at 15:21
  • @leemes well, I have implemented the above algorithm exactly like you mentioned. For example for a rectangle 17x16, your algorithm produced 9 pcs squares and they are 8,8,1,1,2,2,2,7,9. While optimum solution is 8 pcs and they are 5,4,1,6,6,3,7,10. – Mehmet Fide Dec 23 '14 at 16:53
  • @MehmetFide I see. If you did it the same way than me on paper, you're not cutting through the whole remaining paper. In the question, it's not very clear if this is allowed. But to me it sounds like it isn't... – leemes Dec 23 '14 at 22:10
  • This solution is wrong for the input (11,13) – eral Jul 22 '22 at 15:22
10

The greedy algorithm is not optimal. On a 6x5 rectangle, it uses a 5x5 square and 5 1x1 squares. The optimal solution uses 2 3x3 squares and 3 2x2 squares.

To get an optimal solution, use dynamic programming. The brute-force recursive solution tries all possible horizontal and vertical first cuts, recursively cutting the two pieces optimally. By caching (memoizing) the value of the function for each input, we get a polynomial-time dynamic program (O(m n max(m, n))).

David Eisenstat
  • 64,237
  • 7
  • 60
  • 120
  • 2
    That approach won't cover all possible ways to cut a rectangle into integer-sized squares. I am willing to believe that it finds the optimal solution anyway. The solutions not covered by your algorithm look terribly inefficient, when the starting shape is a rectangle. – kasperd Sep 18 '14 at 11:51
  • @kasperd depends on whether "cut along a line" means you can stop midway or not. – David Eisenstat Jun 06 '21 at 12:18
2

This problem can be solved using dynamic programming.

Assuming we have a rectangle with width is N and height is M.

  • if (N == M), so it is a square and nothing need to be done.

  • Otherwise, we can divide the rectangle into two other smaller one (N - x, M) and (x,M), so it can be solved recursively.

  • Similarly, we can also divide it into (N , M - x) and (N, x)

Pseudo code:

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;
Pham Trung
  • 11,204
  • 2
  • 24
  • 43
0

Here is a greedy impl. As @David mentioned it is not optimal and is completely wrong some cases so dynamic approach is the best (with caching).

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)

Here is DP attempt in 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)
harry
  • 1,061
  • 1
  • 13
  • 13
-1

This is essentially classic integer or 0-1 knapsack problem that can be solved using greedy or dynamic programming approach. You may refer to: Solving the Integer Knapsack

Community
  • 1
  • 1
Dr. Debasish Jana
  • 6,980
  • 4
  • 30
  • 69
  • 1
    It's not true that this problem can be solved optimally using a greedy approach, and nor do I see how it's equivalent to the integer knapsack problem. – j_random_hacker Sep 18 '14 at 08:46