/*
* 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;
}
poj 1020 分蛋糕问题
最新推荐文章于 2022-11-09 09:35:48 发布