Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++ 알고리즘: n×m 직사각형을 최소 개수의 정사각형으로 채우는 방법

크기가 n × m인 직사각형이 있다고 가정해 보겠습니다. 이때 변의 길이가 정수인 정사각형 조각들을 사용해 직사각형을 빈틈없이 채울 때 필요한 정사각형의 최소 개수를 구하는 것이 문제입니다.

예를 들어 입력이 n = 2, m = 3이라면 다음과 같습니다.

C++ 알고리즘: n×m 직사각형을 최소 개수의 정사각형으로 채우는 방법

세 개의 정사각형 블록으로 직사각형을 완전히 덮을 수 있으므로 출력값은 3이 됩니다.

해결 접근 방식

이 문제는 DFS(깊이 우선 탐색)와 메모이제이션을 결합하여 해결할 수 있습니다. 핵심 아이디어는 각 열의 현재 채워진 높이를 추적하고, 항상 가장 낮은 위치부터 정사각형을 배치하면서 가능한 모든 경우를 탐색하는 것입니다.

DFS 함수(dfs)의 동작 단계

  • 방문한 상태를 기록할 맵(map) s를 하나 정의합니다.

  • res := 무한대(inf)로 초기화합니다.

  • n, m, 높이 배열 h, 현재 사용 개수 cnt를 매개변수로 받는 dfs() 함수를 정의합니다.

  • cnt >= res이면 즉시 반환합니다. 이미 찾은 답보다 나아질 수 없기 때문입니다(가지치기).

  • isFull := true로 설정합니다.

  • pos := -1, minH := inf로 초기화합니다.

  • i를 1부터 n까지 반복합니다.

    • h[i] < m이면 isFull := false로 갱신합니다(아직 비어 있는 공간이 존재).

    • h[i] < minH이면 minH := h[i], pos := i로 갱신합니다(가장 낮은 열의 위치와 높이를 기록).

  • isFull이 참이면(모든 열이 꽉 찼으면) res := min(res, cnt)로 갱신한 뒤 반환합니다.

  • key := 0, base := m + 1로 설정합니다.

  • i를 1부터 n까지 반복하며 key에 h[i] × base를 누적하고 base를 (m + 1)배씩 곱합니다. 즉, 현재 높이 상태를 하나의 고유한 정수 키로 인코딩합니다.

  • key가 s에 이미 존재하고 s[key] <= cnt라면 반환합니다. 같은 상태를 더 적은 블록 수로 방문한 적이 있다는 의미이므로 더 탐색할 필요가 없습니다.

  • s[key] := cnt를 저장합니다.

  • end := pos로 설정합니다.

  • (end + 1 <= n 이고 h[end + 1] == h[pos] 이고 (end + 1 − pos + 1 + minH) <= m)인 동안 end를 1씩 증가시켜, 해당 위치에 놓을 수 있는 정사각형의 최대 폭을 계산합니다.

  • j를 end부터 pos까지 감소시키며 반복합니다.

    • curH := j − pos + 1 (이번에 놓을 정사각형의 한 변 길이)

    • 크기 n + 1짜리 배열 next를 만들고 h의 값을 복사합니다.

    • k를 pos부터 j까지 반복하며 next[k] += curH를 적용해 정사각형을 배치한 결과를 만듭니다.

    • dfs(n, m, next, cnt + 1)을 재귀 호출합니다.

메인(main) 로직

  • n == m이면 정사각형 하나로 채울 수 있으므로 1을 반환합니다.

  • n > m이면 swap(n, m)으로 두 값을 교환해 항상 n ≤ m이 되도록 합니다.

  • 크기 n + 1짜리 높이 배열 h를 생성합니다(초기값은 모두 0).

  • dfs(n, m, h, 0)을 호출합니다.

  • res를 반환합니다.

다음 구현 예제를 통해 더 자세히 이해해 보겠습니다.

예제 코드

#include <bits/stdc++.h>
using namespace std;
class Solution {
   public:
   map<int, int> s;
   int res = INT_MAX;
   void dfs(int n, int m, vector<int> h, int cnt){
      if (cnt >= res)
         return;
      bool isFull = true;
      int pos = -1, minH = INT_MAX;
      for (int i = 1; i <= n; i++) {
         if (h[i] < m)
            isFull = false;
         if (h[i] < minH) {
            minH = h[i];
            pos = i;
         }
      }
      if (isFull) {
         res = min(res, cnt);
         return;
      }
      long key = 0;
      long base = m + 1;
      for (int i = 1; i <= n; i++) {
         key += h[i] * base;
         base *= m + 1;
      }
      if (s.find(key) != s.end() && s[key] <= cnt)
         return;
      s[key] = cnt;
      int end = pos;
      while (end + 1 <= n && h[end + 1] == h[pos] && (end + 1 - pos + 1 + minH) <= m)
      end++;
      for (int j = end; j >= pos; j--) {
         int curH = j - pos + 1;
         vector<int> next(n + 1);
         for (int i = 1; i <= n; i++)
            next[i] = h[i];
         for (int k = pos; k <= j; k++) {
            next[k] += curH;
         }
         dfs(n, m, next, cnt + 1);
      }
   }
   int tilingRectangle(int n, int m){
      if (n == m)
         return 1;
      if (n > m)
         swap(n, m);
      vector<int> h(n + 1);
      dfs(n, m, h, 0);
      return res;
   }
};
main(){
   Solution ob;
   cout << (ob.tilingRectangle(2, 3));
}

입력

2,3

출력

3