题目 :
完美正方形
如果一些边长互不相同的正方形,可以恰好拼出一个更大的正方形,则称其为完美正方形。请提交紧贴着下边沿的正方形的边长,从左到右,用空格分开。
===================== 华丽的分割线 ===============================
当时在考场上看见这道题目的时候, 我果断选择了跳过, 首先当时就没太多思路, 其次吧, 好不容易有了思路, 觉得貌似需要线段树加深搜暴力破解, 想想肯定要耗不少时间来写代码, 对于一道填空题而言, 不太划算啊, 所有就放弃了. 直到最近, 突然回想起此题, 就花了点时间做了一下( 主要是因为在度娘上没有找到别人发的题解,所以想自己拿个沙发什么的... ).
至于思路, 我们可以效仿[俄罗斯方块]的思想, 在玩俄罗斯方块的时候, 我们总会下意识地把方块放置到高度最低的层上,同理,对于完美正方形的搭建, 由于题目已经规定了上边缘的三个块, 所以我们由上往下搭建, 而且我们知道边长为154, 由此,可以定义 :
1 .完美正方形的上边沿表示为区间[L,R]=[0,153], 区间长度为R-L+1=154 .
2 .当我们没有往里面放置任何小正方形时, 区间[0,153]的高度为0, 且整体平滑.
3 .如果区间[L, R]不平滑, 则其高度定义为-1, 表示无意义.
4 .如果区间[L, R]平滑, 则其子区间[P, Q]平滑( 其中L<=P<=Q<=R)且高度相等.
5 .如果区间[L, R]不平滑,则其父区间[P, Q]不平滑( 其中P<=L<=R<=Q).
6 .每次只能往高度最小的平滑区间内从左向右地放置小正方形M, 但M的边长不能大于区间长度.
大家看明白我的定义了吗 ? 虽然有点难理解, 但我们可以举个例子, 区间[0,153]的初始化高度为0, 整体平滑, 我们向里面放置一个边长为47的小正方形后, 区间[0,153]开始变得不平滑, 则置其高度为-1, 但其子区间[0,46]平滑且高度为47, 区间[47,153]平滑且高度依然为0. 我们继续放置一个边长为46和61的正方形后, 区间[0,153]则被分成了高度不同的三个平滑段, 其中高度最低的46.
有了上述定义, 那我们的目标就是, 每次找出高度最低的平滑区间, 尝试在上面放置正方形, 直到区间[0,153]重新平滑且高度为154时, 结果成立.
为此, 我们需要以下的一些全局变量:
#include
using namespace std;
// 全局变量
namespace Global{
const int MaxS = 46+47+61; // 正方形的边长
int All[] = {2,5,9,11,16,17,19,21,22,
24,26,30,31,33,35,36,41,50,52};// 备选正方形边长
int Length = sizeof(All)/sizeof(All[0]);// 备选正方形的数量
int Square[MaxS][MaxS]={0}; // 表示结果的完美正方形
}
// 线段树结点
struct TNode{
int L, R; // [L, R]
int height; // 段高度( -1时表示该段不平滑,高度无意义 )
int inc; // 高度增量
// 计算宽度
inline int Width()const{ return R-L+1;}
// 计算中点
inline int Mid()const{ return (L+R)>>1; }
}NSet[1024];
inline int LSon( int i ){ return i<<1;}
inline int RSon( int i ){ return LSon(i)+1; }
inline int Parent( int i){ return i>>1;}
// 建立线段树
void BuildTree( int i, int L, int R )
{
TNode* p = NSet+i;
p->L = L;
p->R = R;
p->height = 0;
p->inc = 0;
if( L < R ){
int m = p->Mid();
BuildTree( LSon(i), L, m );
BuildTree( RSon(i), m+1, R);
}
}
主要的问题在于, 如何对指定区间的高度进行增减, 以及在增减后维护其区间平滑性:
// 增量的向下传递调整
// 注: NSet[i]必须为平滑区间
inline void Adjust( int i )
{
if( NSet[i].inc != 0 ){
NSet[i].height += NSet[i].inc;
if( NSet[i].L != NSet[i].R )
{
NSet[LSon(i)].inc += NSet[i].inc;
NSet[RSon(i)].inc += NSet[i].inc;
}
NSet[i].inc = 0;
}
}
// 将区间[L,R]的高度统一增加(或减少)inc
// 注: 区间 [L, R] 必须平滑
void Add(int i, int L, int R, int inc )
{
TNode* p = NSet+i;
if( p->L == L && p->R == R ){
// 操作后平滑性不变
p->inc += inc;
Adjust(i);
}
else{
int m = p->Mid();
if( p->height != -1 ) // 表示该段本是平滑的
{
Adjust(i);
p->height = -1; // 现在开始该段不再平滑
}
int ls = LSon(i), rs = RSon(i);
if( R <= m )
Add( ls, L, R, inc );
else if( L > m )
Add( rs, L, R, inc );
else{
Add( ls, L, m, inc );
Add( rs, m+1, R, inc );
}
// 平滑性恢复检验
if(NSet[ls].height != -1){
Adjust(ls);
if(NSet[rs].height != -1){
Adjust(rs);
if(NSet[ls].height == NSet[rs].height)
p->height = NSet[ls].height;
}
}
} // end else
}
有了上述的线段树定义, 我们接下来需要定义一个能找出最小高度平滑区间的函数:
// 获取最低段区间
void GetLowestInterval(int i, TNode* prev, TNode* lowest )
{
TNode* p = NSet+i;
if( p->height == -1 )
{
GetLowestInterval( LSon(i), prev, lowest );
GetLowestInterval( RSon(i), prev, lowest );
}
else
{
prev->R = p->R;
// 检验等高区间连续性
if( p->height != prev->height )
{
prev->L = p->L;
prev->height = p->height;
}
// 更新最低区间
if( prev->height <= lowest->height )
{
lowest->height = prev->height;
lowest->L = prev->L;
lowest->R = prev->R;
}
}
}
当这些基本的操作都实现后, 我们就可以专注暴力破解了:
bool Dfs( int L, int R ,int H )
{
if( L > R )
{
TNode p, m;
m.height = 1000;
m.L = m.R = 1000;
p.height = 1000;
p.L = p.R = -1;
GetLowestInterval(1, &p, &m);
L = m.L;
R = m.R;
H = m.height;
}
int Width = R-L+1, temp=0;
if( Width == Global::MaxS && H != 0 ) return true;
for( int i = Global::Length; i-- > 0 ;)
{
temp = Global::All[i];
if( temp != 0 && temp <= Width )
{
Global::All[i] = 0;
Add( 1, L, L+temp-1, temp);
Global::Square[ H+temp-1 ][ L ] = temp;
if( Dfs( L+temp, R, H ) == true ) return true;
Add( 1, L, L+temp-1, -temp);
Global::All[i] = temp;
}
}
return false;
}
最后就是主函数啦
int main(int argc, char** argv) {
BuildTree( 1, 0, Global::MaxS-1 );
Add( 1, 0, 46, 47);
Add( 1, 47, 92, 46);
Add( 1, 93, 153, 61);
if( Dfs( 1, 0, 0 ) ) {
for( int i = 0,t=0; i < Global::MaxS; i+=t){
t = Global::Square[ Global::MaxS-1 ][i];
cout << t << ' ';
}
}
return 0;
}
将近200行的代码到此结束了, 我还是觉得, 这种东西真的很难在考场上写出了,量大,细节繁,分又不高, 也行是因为我技术有限吧, 如果有谁有更简便的方法, 也欢迎评论吐槽~~