image.png

BOJ 1691 - 석판

문제를 제대로 안읽었는데 대충 찍어서 맞췄다.


석판은 항상 남은 부분에서 가로나 세로로 쭉 잘라야 한다.

$dp[W][H]$를 $W, H$ 크기의 직사각형에서의 최소 손실이라고 정의한다.

현재 직사각형에서 가로나 세로로 쭈욱 자르며 반으로 나뉘어진 부분에 대해서 또 $dp$ 가 구해져있을 것이므로 점화식은 어렵지않다.

일단 현재 $W, H$에서 $N$개의 석판 중 둘 수 있는걸 아무거나 둬본다. $W$나 $H$에 딱 들어맞는다면 또 직사각형이 생길 것이고 아니라면 ㄱ 자 모양이 남을 것이다.

그런데 석판은 항상 남은 부분에서 가로나 세로로 쭉 잘라야 하므로 ㄱ자 모양은 무조건 두 가지 모양 중 하나로 나뉘어야 한다.

ㄱ자 모양이 꺾이는 부분의 정사각형이 어디에 속해있냐에 따라 다르다.

그러면 나뉠 수 있는 두 가지 경우 중 두 직사각형의 손실의 합중 최소를 구하면 된다.

void solve() {
   int Y, X, n;
   cin >> Y >> X >> n;
   vector<pi> a(n);
   for (auto &[h, w]: a) cin >> h >> w;
   auto dp = vec(Y + 1, X + 1, -1);
   // 최소 손실값
   function<int(int, int)> fn = [&](int h, int w) -> int {
      if (h <= 0 || w <= 0) return 0;
      int &ret = dp[h][w];
      if (~ret) return ret;
      ret = h * w;

      for (int i = 0; i < n; i++) {
         if (a[i].fi > h || a[i].se > w) continue;
         int extra_h = h - a[i].fi;
         int extra_w = w - a[i].se;

         int right_long = fn(h, extra_w);
         int right_short = fn(a[i].fi, extra_w);
         int bottom_long = fn(extra_h, w);
         int bottom_short = fn(extra_h, a[i].se);

         mina(ret, right_long + bottom_short);
         mina(ret, right_short + bottom_long);
      }

      return ret;
   };
   cout << fn(Y, X);
}

Tags:

Categories:

Updated:

Comments