铺放矩形块

GitHub跳转原题关系图返回列表

枚举 4 块矩形的全排列与横竖方向,对 6 种基本铺放拓扑用 O(1) 宽高公式算最小包围盒,去重输出所有边长解。

OJ: roj

题目 ID: 2014

难度:普及-

标签:枚举模拟几何

创建: 2026-10-01 02:58

更新: 2026-10-06 09:36

题目描述

给定 4 个矩形块,边长范围 1~50。把它们不重叠地放入一个边与矩形块平行的封闭矩形,使面积最小。输出最小面积及所有取到最小面积的边长 P Q(P ≤ Q),按 P 升序。

样例输入/输出

text
1 2        40
2 3        4 10
3 4        5 8
4 5

思路

任意合法铺放都可由 6 种基本方案经旋转、镜像得到。枚举 4 块矩形的排列、每块是否旋转,对 6 种方案用 O(1) 宽高公式算最紧包围盒,取面积最小者去重输出。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 03:05
# update_at: 2026-10-01 03:05

import sys
from itertools import permutations, product

BASE = 4          # 题目固定 4 个矩形块


def enclosing_sizes(placed: list[tuple[int, int]]) -> list[tuple[int, int]]:
    """计算六种基本铺放方案的封闭矩形 (宽, 高);方案 4、5 实为同一拓扑。

    前置约定:placed 已按方案图的编号排好序——方案 1~3 中矩形 4
    在右侧/下方独立伸出,方案 4~6 中矩形 1、3 叠放在左柱,
    矩形 2、4(方案 6 记号里为 2、3)分跨右柱与横梁。
    """
    w1, h1, w2, h2, w3, h3, w4, h4 = sum(placed, ())
    beam = (w1 + max(w2, w4) + w3, max(h1, h3, h2 + h4))          # 方案 4/5:双柱加横梁
    # 方案 6:风车形。凸出矩形只盖住自己柱子时,另一柱可以完全嵌进缺口里
    if h3 >= h2 + h4:
        width = max(w1, w2 + w3, w3 + w4)
    elif h3 > h4:                                                  # 矩形 3 凸出,矩形 4 需让出宽 w1+w2
        width = max(w1 + w2, w2 + w3, w3 + w4)
    elif h4 > h3:                                                  # 矩形 4 凸出,矩形 3 需让出宽 w1+w2
        width = max(w1 + w2, w1 + w4, w3 + w4)
    elif h4 >= h1 + h3:                                            # 矩形 4 只盖住矩形 1,矩形 3 完全嵌进
        width = max(w2, w1 + w4, w3 + w4)
    else:                                                          # h3 == h4:两柱平齐,谁也不盖谁
        width = max(w1 + w2, w3 + w4)
    pinwheel = (width, max(h1 + h3, h2 + h4))
    return [
        (w1 + w2 + w3 + w4, max(h1, h2, h3, h4)),                  # 方案 1:一字排开
        (max(w1 + w2 + w3, w4), max(h1, h2, h3) + h4),             # 方案 2:三下一上
        (max(w1 + w2, w3) + w4, max(h1 + h3, h2 + h3, h4)),        # 方案 3:两左一右上,矩形 4 立在右侧
        beam,
        pinwheel,
    ]


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    blocks = [(next(data), next(data)) for _ in range(BASE)]

    best_area = sys.maxsize                                        # 最小封闭矩形面积
    solutions: set[tuple[int, int]] = set()                        # 取到最小面积的所有 (p, q)
    for order in permutations(blocks):                             # 枚举每个图位放哪个矩形
        for turned in product(*[((a, b), (b, a)) for a, b in order]):  # 枚举每个矩形横放/竖放
            for width, height in enclosing_sizes(list(turned)):
                area = width * height
                if area < best_area:
                    best_area = area
                    solutions = {(min(width, height), max(width, height))}
                elif area == best_area:
                    solutions.add((min(width, height), max(width, height)))

    print(best_area)
    for p, q in sorted(solutions):
        print(p, q)


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 09:33
 * update_at: 2026-10-06 09:33
 */
#include <iostream>
#include <set>
#include <utility>
#include <climits>
#include <algorithm>
using namespace std;

typedef long long ll;

const int BASE = 4;             // 固定 4 个矩形块

ll a[BASE][2];                  // 输入的 4 个矩形块边长
ll order[BASE];                 // 当前排列:order[i] 表示第 i 个图位放的原始矩形编号
ll orient[BASE];                // 当前方向:0 表示 a×b,1 表示 b×a
ll best_area;                   // 当前最小面积
set<pair<ll, ll> > ans;         // 取到最小面积的所有规范化边长 (p, q)

// 用当前排列和方向得到第 k 个图位矩形的宽 w[k]、高 h[k]
void get_rect(ll w[], ll h[]) {
    for (int i = 0; i < BASE; i++) {
        ll idx = order[i];
        if (orient[i] == 0) {
            w[i] = a[idx][0];
            h[i] = a[idx][1];
        } else {
            w[i] = a[idx][1];
            h[i] = a[idx][0];
        }
    }
}

// 尝试用 (宽, 高) 更新答案集合
void update(ll width, ll height) {
    ll area = width * height;
    ll p = min(width, height);
    ll q = max(width, height);
    if (area < best_area) {
        best_area = area;
        ans.clear();
        ans.insert(make_pair(p, q));
    } else if (area == best_area) {
        ans.insert(make_pair(p, q));
    }
}

// 计算 5 种基本铺放方案(方案 4、5 为同一拓扑)的封闭矩形并更新答案
void calc_all() {
    ll w[BASE], h[BASE];
    get_rect(w, h);

    ll w1 = w[0], h1 = h[0];
    ll w2 = w[1], h2 = h[1];
    ll w3 = w[2], h3 = h[2];
    ll w4 = w[3], h4 = h[3];

    // 方案 1:4 个矩形一字排开
    update(w1 + w2 + w3 + w4, max(max(h1, h2), max(h3, h4)));

    // 方案 2:下面并排 3 个,上面压 1 个
    update(max(w1 + w2 + w3, w4), max(max(h1, h2), h3) + h4);

    // 方案 3:左侧上下叠 2 个,右侧下方 1 个,右上方 1 个
    update(max(w1 + w2, w3) + w4, max(max(h1 + h3, h2 + h3), h4));

    // 方案 4/5:双柱加横梁
    update(w1 + max(w2, w4) + w3, max(max(h1, h3), h2 + h4));

    // 方案 6:风车形,按高度关系分 5 类求宽度
    ll width6;
    if (h3 >= h2 + h4) {
        width6 = max(max(w1, w2 + w3), w3 + w4);
    } else if (h3 > h4) {
        width6 = max(max(w1 + w2, w2 + w3), w3 + w4);
    } else if (h4 > h3) {
        width6 = max(max(w1 + w2, w1 + w4), w3 + w4);
    } else if (h4 >= h1 + h3) {
        width6 = max(max(w2, w1 + w4), w3 + w4);
    } else { // h3 == h4
        width6 = max(w1 + w2, w3 + w4);
    }
    update(width6, max(h1 + h3, h2 + h4));
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    for (int i = 0; i < BASE; i++) {
        cin >> a[i][0] >> a[i][1];
    }

    // 初始化排列为 0,1,2,3
    for (int i = 0; i < BASE; i++) {
        order[i] = i;
    }

    best_area = LLONG_MAX;
    ans.clear();

    // 枚举 4! 排列
    do {
        // 枚举 2^4 方向
        for (int mask = 0; mask < (1 << BASE); mask++) {
            for (int i = 0; i < BASE; i++) {
                orient[i] = (mask >> i) & 1;
            }
            calc_all();
        }
    } while (next_permutation(order, order + BASE));

    cout << best_area << "\n";
    for (set<pair<ll, ll> >::iterator it = ans.begin(); it != ans.end(); it++) {
        cout << it->first << " " << it->second << "\n";
    }

    return 0;
}