对每组数据输出一行"yes"或"no",表示能否用全部积木拼成一个正方形.
解题思路:
鉴于积木的个数不多,可以采用深度优先的方式进行搜索;
首先,要组成的大正方形的表示:采用一维数组cols[41]来表示,其中cols[i]表示第i列中已占用的行的行号加1,即可用的第一行行号。为什么为41,是因为要组成的正方形可能的最大边长41,而我们让下标从1开始。
然后,输入的积木的边的保存:还是采用一维数组side[11],其中side[i]表示边长为i的边的数目,这里的11同上,也是由于最大为10,而我们从side[1]开始。
搜索的策略,很容易想到可以从矩阵的左上角开始填,每次按列从左边向右填,左边的填完了才能天右边的,显然这样是正确的。
优化与剪枝,首先,所有小蛋糕的面积和不等于要拼的正方形的面积,则没必要填。可以每次按正方形从大到小开始填,如果该大小的正方形填不进去,则其他相同大小的正方形就没有必要所了,回溯后,原先大小的正方形也没必要填,因为正方形最大是10,所以从10开始选,一直递减,看有没有该大小的正方形,能不能放入,如果放不下则选择比其小的正方形即可,否则,该正方形的数目减一,修改该矩形,搜索下一个正方形,如果该拼法行不通,则回溯。
C++代码如下:
#include
#include
using namespace std;
int
s; //s,表示要组成的正方形的边长
int
n; //n(1<=n<=16),表示积木的个数
int
side[11]; //side[i]用来保存边长为i的小正方形的个数,这里约定正方形的边长最大为10
int
cols[41]; //cols[i]表示第i列已经被占用的行数+1,即最小可用行起始
vector result;
int backTrace(int a)
{
int selectCol, minRow, i, j, flag;
if (a == n) return 1;
minRow = 41;
for (i = 1; i <= s; ++i)
if (cols[i] <
minRow)
{
selectCol =
i;
minRow =
cols[i];
}
for (i = 10; i > 0; --i)
{
if (side[i]
&& selectCol-1+i <=
s && minRow-1+i <=
s)
{
flag =
1;
for (j =
selectCol; j < selectCol + i; ++j)
if
(cols[j] > minRow)
{
flag
= 0;
break;
}
if
(flag)
{
for
(j = selectCol; j < selectCol + i; ++j) cols[j] +=
i;
side[i]--;
if
(backTrace(a + 1)) return 1;
else
{
for
(j = selectCol; j < selectCol + i; ++j) cols[j] -=
i;
side[i]++;
}
}
}
}
return 0;
}
int main()
{
int cases = 0;
int i, temp;
cin>>cases;
int area;
while (cases > 0)
{
cases--;
cin>>s>>n;
memset(side, 0,
sizeof(side));
area = 0;
for (i = 0; i <
n; ++i)
{
cin>>temp;
side[temp]++;
area += temp
* temp;
}
if (area != s * s)
result.push_back(0);
else
{
for (i = 1; i
<= 40; ++i) cols[i] = 1;
int rr =
backTrace(0);
result.push_back(rr);
}
}
for (i = 0; i < result.size();
++i)
if (result[i])
cout<
else
cout<
return 0;
}