快轉到主要內容

CSES-1744 Rectangle Cutting

目錄

題目連結:https://cses.fi/problemset/task/1744

題意
#

給你一個長度為 \( n \)、寬度為 \( m \) 的長方形。
你每次操作可以拿刀把其中一塊長方形切成兩塊更小的長方形(長寬必須保持為整數)。請問你最少需要切幾刀,才能把整個大長方形全部切成正方形?

思路
#

這是一題標準的「區間 DP」或是分割型 DP。

我們可以建立一個二維陣列 dp,其中 dp[i][j] 代表「要把一個 \( i \times j \) 的長方形完全切成正方形,最少需要的刀數」。

首先思考最簡單的情況:如果這個長方形本來就是正方形(也就是 i == j),那我們連一刀都不用切,答案直接是 0。

如果 ij 不一樣長,我們就勢必要切一刀把它分成兩半。而這一刀有兩種切法:

  1. 橫切:我們可以在高度 i 的任意位置 k 切一刀,把它分成上下兩塊,上面是 k*j,下面是 (i-k)*j。這樣切完的總刀數就是 dp[k][j] + dp[i-k][j] + 1
  2. 直切:同理,我們可以在寬度 j 的任意位置 k 切一刀,把它分成左右兩塊,左邊是 i*k,右邊是 i*(j-k)。總刀數則是 dp[i][k] + dp[i][j-k] + 1

我們只要把所有切法(所有的可能 k)都嚐試一遍,從中挑出讓總刀數最少的那一種,並把這個最小值記錄到 dp[i][j] 裡面。透過由小到大的尺寸慢慢推算,最後 dp[n][m] 就會是我們要的答案了。

程式碼
#

#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define fi first
#define se second
#define INF LONG_LONG_MAX/1000
#define WA() cin.tie(0)->sync_with_stdio(0)
#define all(x) (x).begin(), (x).end()
#define int long long
#define PII pair<int, int>

signed main() { WA();
    int n, m; cin >> n >> m;
    
    // dp[i][j] 代表 i*j 長方形切成正方形的最少刀數
    vector<vector<int>> dp(n+1, vector<int>(m+1));
    
    // 初始化極端情況:如果有一邊寬度是 1,長度是多少就只能一塊一塊切,要切長度 - 0 刀 (i-0=i 這裡程式碼將0的部分設為自己,但主要邏輯由 DP 取 min 覆寫)
    // (這裡 dp 初始化的概念是防呆用的,更精確的邊界其實是 i == j 時為 0,見迴圈控制)
    for (int i = 1; i <= n; i++) dp[i][0] = i;
    for (int i = 1; i <= m; i++) dp[0][i] = i;
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            // 如果本身就是正方形,不用切,保持預設的 0
            if (i == j) continue;
            
            int mn = INF;
            // 嘗試所有橫切的可能 (在高度 k 的位置切一刀)
            for (int k = 1; k < i; k++) mn = min(mn, dp[i-k][j] + dp[k][j] + 1);
            // 嘗試所有直切的可能 (在寬度 k 的位置切一刀)
            for (int k = 1; k < j; k++) mn = min(mn, dp[i][j-k] + dp[i][k] + 1);
            
            // 找出所有切法中的最小值
            dp[i][j] = mn;
        }
    }
    
    cout << dp[n][m] << '\n';
}
Piau 的筆記本
作者
Piau 的筆記本
希望我寫下來的東西能夠長久的記在我的腦中