題目大意
有一塊邊長爲BoxSize的正方形的大蛋糕,現在給出n塊不同尺寸的正方形的小蛋糕的邊長,問是否能把大蛋糕按恰好切割爲這n塊小蛋糕,要求每塊小蛋糕必須爲整塊。
解題思路
有技巧的DFS
可以把大蛋糕想象爲一個蛋糕盒子,然後往裏面裝小蛋糕。
裝蛋糕時遵循以下原則:
自下而上,自左至右;
即先裝好盒子底部,再繼續往上層裝,且裝每一層時都靠左邊放蛋糕;
大蛋糕優先裝,因爲小蛋糕靈活度比較高。
只要把問題變換爲上述問題,我想對深搜比較熟悉的同學也會馬上得到思路了,這個只是很簡單的DFS思路。
但是本題的難點不在於怎樣去DFS,而是每放入一個蛋糕後,怎樣去標記盒子已經放有蛋糕的位置?
我初始的做這題時,因爲看到數據規模不大(Max_n=16,Max_size=10,那麼大蛋糕最大也就),於是我把尺寸爲BoxSize的盒子劃分爲的格子,每放入一個大小爲size的蛋糕,就用一個二重循環去標記的格子。
最後是毫無懸念地TLE了。
看了別人的方法,發現或分格子的思路是正確的,但應該“按列標記”。不但把盒子看做多個個格子,也把小蛋糕看做多個的單位,建立一個一維數組,每放入一個蛋糕,則去記錄每列的格子被填充的數目。
例如在第2~4列放入了一個的小蛋糕,那麼。有同學會問,爲什麼行不用計數?要是放入蛋糕後,該蛋糕底部出現部分懸空怎麼處理?這個情況是不會出現的,因爲當前DFS遵循先把底部放滿原則,要是出現懸空,則會回溯。
更具體的處理方法請看程序註釋。
//Memory Time
//208K 32MS
#include<iostream>
using namespace std;
int BoxSize; //盒子尺寸
int n; //蛋糕的總個數
int SizeNum[11]; //各種尺寸的蛋糕個數
int col[41]; //把盒子縱行分割成BoxSize*BoxSize個1*1大小的小格子
//col[i]記錄第i列被填充了的格子數
bool DFS(int FillNum) //FillNum:已放入盒子的蛋糕數
{
if(FillNum==n)
return true;
/*尋找格子數被填充最少的列,靠左優先*/
int min=50;
int prow;
for(int i=1;i<=BoxSize;i++)
if(min>col[i])
{
min=col[i];
prow=i;
}
/*枚舉各種尺寸的蛋糕自下而上地放入盒子*/
for(int size=10;size>=1;size--)
{
if(!SizeNum[size])
continue;
//檢查尺寸爲size的蛋糕放入盒子時在縱向和橫向是否越界
if(BoxSize-col[prow]>=size && BoxSize-prow+1>=size)
{
//檢查盒子從第prow列到第prow+size-1列,共size列的寬度wide中
//是否每列剩餘的空間都足夠放入高度爲size的蛋糕
int wide=0;
for(int r=prow;r<=prow+size-1;r++)
{
if(col[r]<=col[prow]) //比較各列的"填充數"
{ //注意,這裏若比較"未填充數"BoxSize-col[r]<size會TLE
wide++; //雖然兩個條件等價,但確實計算了3秒左右,不知何故
continue;
}
break;
}
if(wide>=size)
{
int r;
//放入尺寸爲size的蛋糕
SizeNum[size]--;
for(r=prow;r<=prow+size-1;r++)
col[r]+=size;
if(DFS(FillNum+1))
return true;
//回溯
SizeNum[size]++;
for(r=prow;r<=prow+size-1;r++)
col[r]-=size;
}
}
}
return false;
}
int main(void)
{
int test;
cin>>test;
for(int t=1;t<=test;t++)
{
memset(SizeNum,0,sizeof(SizeNum));
memset(col,0,sizeof(col));
cin>>BoxSize>>n;
int cnt=0; //記錄size>(BoxSize/2)的蛋糕個數
int area=0; //計算所有蛋糕的面積之和
for(int i=1;i<=n;i++)
{
int size;
cin>>size;
area+=size*size;
SizeNum[size]++;
if(size>BoxSize/2)
cnt++;
}
if(cnt>1 || area!=BoxSize*BoxSize)
{
cout<<"HUTUTU!"<<endl;
continue;
}
if(DFS(0))
cout<<"KHOOOOB!"<<endl;
else
cout<<"HUTUTU!"<<endl;
}
return 0;
}