思路分析:
AC 代碼: 16 ms
// 思路:這個問題沒有巧妙的思路,於是只有貪心:能放下就放下,湊失敗了就換一個。
// 將大蛋糕人爲劃分爲 SIZE * SIZE 個小格子,然後“從上到下,從左到右”依次填充大蛋糕。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
/*************** 全局變量 ******************/
char G[42][42];
int SIZE, n; // 大蛋糕尺寸爲 SIZE * SIZE,小蛋糕有 n 個
int cases; // 測試用例的數量
int a[18]; // 所有小蛋糕的邊長
bool visit_a[18]; // 所有小蛋糕被使用的情況
int col[42]; // 記載每一列被佔用的格子數
bool visit_map[18]; // 所有小蛋糕被使用的情況,是否被使用了
bool is_OK; // 本問題是否可解
bool cmp(int a, int b){
return a > b;
}
int min(int a, int b){
return a > b ? b : a;
}
// used_cakes: 已經使用的小蛋糕數量
// col 是核心的數據,描述了當前大蛋糕被填充的情況
//
void dfs(int used_cakes){
// Step 1: Termination condition
if (used_cakes == n || is_OK == true){
is_OK = true;
return;
}
// Step 2: 確定搜索範圍
// 根據剪枝1,從前往後選
for (int i = 0; i < n && is_OK==false; i++){
// Step 3: 排除無效的候選對象
if (visit_a[i] == true) continue;
// 從前往後找,找某一列其佔用格子數最少
// 坑:要找首次出現的佔用格子數最少的列,所以,搜索的順序應該是從後往前找,或者判斷條件爲 < 而不是 <= (細節)
int min_ind = -1, min_val = 999999;
for (int j = 0; j <SIZE; j++)
if (col[j] < min_val) {
min_ind = j;
min_val = col[j];
}
if ( SIZE - col[min_ind] < a[i] || min_ind + a[i] -1 >= SIZE || col[min_ind+a[i]-1] != col[min_ind] )
continue; // 爲了加速程序,此處快速判斷
int w_x = 0; // 水平方向能容納的最大寬度
int w_y = 0; // 垂直方向能容納的最大寬度
int w = 0; // 綜合來看,當前能容納的最大寬度
for (int j = min_ind; j < SIZE;j++)
if (col[j] <= min_val)
w_x++;
else
break;
w_y = SIZE - col[min_ind];
w = min(w_x, w_y);
// 判斷是否能放得下
if (a[i] > w) continue;
// Step 4: 訪問當前結點(即,使用當前小蛋糕)
visit_a[i] = true;
int new_val = col[min_ind] + a[i];
for (int j = min_ind; j <= min_ind + a[i] - 1; j++)
col[j] += a[i]; // 之所以不是 col[j] += a[i],是考慮到了像俄羅斯方塊一樣,前一層有缺口
// Step 5: 繼續下一步的搜索
dfs(used_cakes + 1);
if (is_OK == true) return;
// Step 6: 放棄使用當前這個小蛋糕!
for (int j = min_ind; j <= min_ind + a[i] - 1; j++)
col[j] -= a[i];
visit_a[i] = false;
}
}
// used_cakes: 已經使用的小蛋糕數量
// col 是核心的數據,描述了當前大蛋糕被填充的情況
//
void dfs_fast(int used_cakes){
// Step 1: Termination condition
if (used_cakes == n || is_OK == true){
is_OK = true;
return;
}
// Step 2: 確定搜索範圍
// 剪枝:如果尺寸爲 x 的蛋糕,導致了失敗,則後面跳過所有尺寸爲 x 的蛋糕
int failure_size = -1;
for (int i = 0; i < n && is_OK == false; i++){
// Step 3: 排除無效的候選對象
if (visit_a[i] == true || a[i] == failure_size) continue;
// 從前往後找,找某一列其佔用格子數最少
// 坑:要找首次出現的佔用格子數最少的列,所以,搜索的順序應該是從後往前找,或者判斷條件爲 < 而不是 <= (細節)
int min_ind = -1, min_val = 999999;
for (int j = 0; j <SIZE; j++)
if (col[j] < min_val) {
min_ind = j;
min_val = col[j];
}
if (SIZE - col[min_ind] < a[i] || min_ind + a[i] - 1 >= SIZE || col[min_ind + a[i] - 1] != col[min_ind])
continue; // 爲了加速程序,此處快速判斷
int w_x = 0; // 水平方向能容納的最大寬度
int w_y = 0; // 垂直方向能容納的最大寬度
int w = 0; // 綜合來看,當前能容納的最大寬度
for (int j = min_ind; j < SIZE; j++)
if (col[j] <= min_val)
w_x++;
else
break;
w_y = SIZE - col[min_ind];
w = min(w_x, w_y);
// 判斷是否能放得下
if (a[i] > w) continue;
// Step 4: 訪問當前結點(即,使用當前小蛋糕)
visit_a[i] = true;
int new_val = col[min_ind] + a[i];
for (int j = min_ind; j <= min_ind + a[i] - 1; j++)
col[j] += a[i]; // 之所以不是 col[j] += a[i],是考慮到了像俄羅斯方塊一樣,前一層有缺口
// Step 5: 繼續下一步的搜索
dfs_fast(used_cakes + 1);
if (is_OK == true)
return;
else if (used_cakes == 0)
return;
else
failure_size = a[i];
// Step 6: 放棄使用當前這個小蛋糕!
for (int j = min_ind; j <= min_ind + a[i] - 1; j++)
col[j] -= a[i];
visit_a[i] = false;
}
}
int main(int argc, char * argv[])
{
cin >> cases;
while (cases--){
/*************** Input ****************/
// Initialization
memset(G, 0, sizeof(G));
memset(a, 0, sizeof(a));
memset(visit_a, 0, sizeof(G));
memset(col, 0, sizeof(col));
is_OK = false;
// Input
int area = 0;
int num_big_cakes = 0;
scanf("%d%d", &SIZE, &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
area += a[i] * a[i];
if (a[i] > SIZE / 2) num_big_cakes++;
}
if (area != SIZE * SIZE || num_big_cakes > 1) {
cout << "HUTUTU!" << endl;
continue;
}
// Sort
sort(a, a + n, cmp);
/*************** Process ****************/
dfs_fast(0);
/*************** Output ****************/
if (is_OK)
cout << "KHOOOOB!" << endl;
else
cout << "HUTUTU!" << endl;
}
return 0;
}