題目
在規定 M * N 大小的地圖,放置 n 個矩形,是否能夠放下,若能,請給出排布結果。
輸入:M, N, n, n 個矩形的長寬。
分析
- n個矩形必須要一個一個放入地圖,當放不下時回退 ---- DFS
- 為什麼某些放置策略會失敗?
矩形的橫縱不對,矩形的位置不對,矩形的放置順序不對 - 放置策略
- 放置順序:應當先放大塊矩形,再放小塊矩形。大小並不是按面積,而是按 max(長, 寬) 排,使不出現前序塊別住當前矩形的情況;
- 橫縱:先考慮橫放,不行就嘗試縱放;
- 放置位置:像俄羅斯方塊一樣,嘗試放在每一行的最左邊,直到放在行首為止。注意,當連續兩行的最左可放置位置一樣時,可跳過後面行,因為前一行都不可放置了,第二行這樣放置將產生更多碎片,更放不下。
實現
處理輸入
#include <iostream>
#include <vector>
#include <bitset>
#include <algorithm>
using namespace std;
int M = 0, N = 0, AREA = 0;
int main() {
cin >> M >> N;
AREA = M * N;
int n = 0;
cin >> n;
// 分別存一份橫縱狀態下的長寬
vector<pair<int, int>> blocks0(n);
vector<pair<int, int>> blocks1(n);
for (int i = 0, tmp1 = 0, tmp2 = 0; i < n; ++i) {
cin >> tmp1 >> tmp2;
if (tmp1 > M || tmp1 > N) {
cout << "放不下!" << endl;
return -1;
}
if (tmp2 > M || tmp2 > N) {
cout << "放不下!" << endl;
return -1;
}
sumArea += tmp1 * tmp2;
if (sumArea > AREA) {
cout << "放不下!" << endl;
return -1;
}
if (tmp1 > tmp2) {
blocks0[i].first = tmp1;
blocks0[i].second = tmp2;
}
else {
blocks0[i].first = tmp2;
blocks0[i].second = tmp1;
}
}
sort(blocks0.begin(), blocks0.end(), [](const pair<int, int>& p1, const pair<int, int>& p2) {
if (p1.first == p2.first) {
return p1.second > p2.second;
}
return p1.first > p2.first;
});
blocks1.assign(blocks0.begin(), blocks0.end());
for (auto& p : blocks1) {
swap(p.first, p.second);
}
// ··· 運算和輸出···
}
計算----DFS
引數設定:
- ans矩陣:記錄矩形放置的位置和方向
- isFilled矩陣:記錄當前地圖被填放的情況
- curBlock:記錄當前要放置的矩陣編號
vector<vector<pair<int ,bool>>> ans(M, vector<pair<int, bool>>(N, pair<int, bool>(-1, false)));
vector<vector<bool>> isFilled(M, vector<bool>(N, false));
myFill(blocks0, blocks1, ans, isFilled, 0);
細說myFill()實現
DFS探索過程
bool myFill(const vector<pair<int, int>>& blocks0, const vector<pair<int, int>>& blocks1,
vector<vector<pair<int, bool>>>& ans, vector<vector<bool>>& isFilled,int curBlock) {
// 所有矩形都放完了,返回成功
if (curBlock == blocks0.size()) {
return true;
}
// 橫向放置
int nextLine = 0;
// 逐行檢查能否橫向放下,直到行首非isFilled的行也放不下就停止檢查
for (int nextPlace = 0, prePlace = -1; nextLine + blocks0[curBlock].second <= M; ++nextLine) {
// 若該行能放下就繼續DFS
if ((nextPlace = canSet(isFilled, nextLine, blocks0[curBlock], prePlace)) != -1) {
// 記錄路徑
ans[nextLine][nextPlace].first = curBlock;
ans[nextLine][nextPlace].second = false;
// 放下一矩形
if (myFill(blocks0, blocks1, ans, isFilled, curBlock + 1)) {
return true;
}
// 失敗撤回
unSet(isFilled, nextLine, nextPlace, blocks0[curBlock]);
ans[nextLine][nextPlace].first = -1;
// 輔助判斷對齊的行
prePlace = nextPlace;
}
// 直到行首非isFilled的行也放不下就停止檢查
if (isFilled[nextLine][0] == false) {
break;
}
}
// 縱向放置,同上,只是此時用的資料集為縱向排布版本blocks1
nextLine = 0;
for (int nextPlace = 0, prePlace = -1; nextLine + blocks1[curBlock].second <= M; ++nextLine) {
if ((nextPlace = canSet(isFilled, nextLine, blocks1[curBlock], prePlace)) != -1) {
if (curBlock == 1) {
curBlock = 1;
}
ans[nextLine][nextPlace].first = curBlock;
ans[nextLine][nextPlace].second = true;
if (myFill(blocks0, blocks1, ans, isFilled, curBlock + 1)) {
return true;
}
unSet(isFilled, nextLine, nextPlace, blocks1[curBlock]);
ans[nextLine][nextPlace].first = -1;
prePlace = nextPlace;
}
if (isFilled[nextLine][0] == false) {
break;
}
}
return false;
}
矩形對地圖的填入和移除操作
// 判斷某矩形是否可以放在第line行。若可以,填充isFilled矩陣,返回位置縱座標;若不可以,返回-1。
// prePlace避免重複判斷對齊的行
int canSet(vector<vector<bool>>& isFilled, int line, const pair<int, int >& block, int prePlace) {
int beg = 0, end = 0;
// 找到該行第一個非isFilled的位置
for (; beg < N && isFilled[line][beg]; ++beg);
// 若該行餘下位置不夠 或 連續對齊行,返回-1
if (beg + block.first > N || beg == prePlace) {
return -1;
}
// 檢查餘下連續矩形寬度個位置都非isFilled
for (end = beg + 1; end < beg + block.first && !isFilled[line][end]; ++end);
// 填充
if (end == beg + block.first) {
for (int i = line; i < line + block.second; ++i) {
for (int j = beg; j < end; ++j) {
isFilled[i][j] = true;
}
}
return beg;
}
// 無法放下返回-1
return -1;
}
// 撤回該位置矩形的isFilled標記
void unSet(vector<vector<bool>>& isFilled, int line, int place, const pair<int, int >& block) {
for (int i = line; i < line + block.second; ++i) {
for (int j = place; j < place + block.first; ++j) {
isFilled[i][j] = false;
}
}
return;
}