poj 1020 分蛋糕問題
阿新 • • 發佈:2019-01-09
/* * poj 1020 切蛋糕 題目大意: 求能否將一堆小正方形無重疊的拼接成一個大正方形? 解題思路: 回溯 + 剪枝 按照從左到右,從上到下的順序,列舉所有可以放置正方形的單元,嘗試放入合適的小正方形。 直到無法放入小正方形或者完全放完為止。 1、因為小正方形的邊長介於1~10之間,所以可以採用計數的方式來儲存小正方形的個數。 2、採用每列佔用數的方式來儲存大正方形的佔用情況,同時用來查詢下一個可能的安置單元。 3、每次選擇佔用最少的列作為安置點 -- 該點必然是某個正方形的左上點,因為它的左上沒有其他空置點了! 4、從大到小選擇小正方形,如果最終所有小正方形可以拼成大正方形的話,所有的小正方形都會用到的, 同等的情況下選擇大的,等於把更靈活、容易安排的小的放在了後面,有點貪心的感覺。但是如果 選擇大的不成功,不等於整個拼接就失敗了,應該選擇更小的正方形進行嘗試。也就是傳說中的回溯。 剪枝: 1、在安置點(最少佔用列)嘗試放置剩餘的最大正方形時,出現超出邊界的情況,可以直接返回false 因為剩餘空間最大的列居然都無法滿足某個剩餘正方形,則該正方形必然無法放下,因此無需進行 進一步的搜素 2、在安置點嘗試放置某邊長為a的小正方形失敗,則所有後續為a的小正方形也無需嘗試,直接進行 更小邊長正方形的嘗試! 最後: 貪心的反例: 10 8 4 4 3 3 3 3 3 3 3 1 1 1 1 1 -> 邊長為10的大正方形,與2個4x4、7個3x3、5個1x1的小正方形 如果貪心,會有如下解: 4x4 4x4 空 空 3x3 3x3 3x3 空 3x3 3x3 3x3 空 最終剩餘一個3x3的小正方形無處安放。 其實該例有解: 3x3 3x3 4x4 111 3x3 3x3 3x3 1 3x3 3x3 1 4x4 */ #include <iostream> #include <cstring> namespace { using namespace std; const int S_MAX = 40; int c[11], d[S_MAX]; // c儲存邊長為i的小正方形的個數,d儲存每例的佔用情況 int n, S; bool splitable(int deepth) { // 所有正方形均被成功安置,返回成功 if (deepth == n) return true; // 尋找安置點,從左到右,從上到下 int x=S, y; for (int j=0; j<S; ++j) { if (d[j] < x) { x = d[j]; y = j; } } // 從大到小嚐試安置小正方形 for (int i=10; i>0; --i) { if(c[i]==0) continue; if(x+i > S) return false; // 剪枝1 if(y+i > S) continue; // 該處不能剪,y到頭表示應該換更小的正方形來嘗試,因為最終每個單元都要被用掉 // 檢視是否足夠單元 bool bEnough = true; for (int k=1; k<i; k++) { if (d[y+k] > d[y]) { bEnough = false; break; } } if (!bEnough) continue; // 深搜 c[i]--; for (int k=0; k<i; k++) d[y+k] += i; if (splitable(deepth+1)) { return true; // 某次成功即返回 } // 回溯 c[i]++; for (int k=0; k<i; k++) d[y+k] -= i; } return false; // 所有的正方形均嘗試過,失敗 } } int main() { int t; cin >> t; int sum, si; for (int k=0; k<t; k++) { cin >> S >> n; sum = 0; // 記得每次清0,否則只有第一次準 memset(c, 0, sizeof(c)); for (int i=1; i<=n; i++) { cin >> si; c[si]++; sum += si * si; } memset(d, 0, sizeof(d)); if (sum==S*S && splitable(0)) { cout << "KHOOOOB!" << endl; } else { cout << "HUTUTU!" << endl; } } return 0; }