原题:
题目2:拼图游戏
Time Limit: 1000ms, Special Time Limit:2500ms, Memory Limit:32768KB
Problem description
你要将一些图形拼成一个正方形,图形的个数从1到5。如下图所示,图形个数是4,
图形不能旋转,拼的时候不能重叠,拼完后的正方形里面不能有空隙。所有给定的图形都要使用。
左面的图表示这样拼不行,右面是一个成功的拼法。
你要判断是否存在一种成功的拼法。如果存在,你要打印出一种拼法。
Input
第一行是一个整数n,表示图形的个数,范围从1到5。
接下来有n个部分,每个部分的第一行是2个整数 i 和 j ,表示下面的 i 行 j 列用来描述一个图形。图形用 0 和 1 表示,1 表示图形占有这个位置,0 表示不占有,中间没有空格。例如上图中图形A的描述是
2 3
111
101
所有图形的长与宽都不超过4。
根据图形给出的顺序给每个图形编号,从1开始,至多到5。
Output
如果不能拼成一个4*4的正方形,就输出“No solution possible”;否则,输出一种拼的方案:一个4*4的正方形的数阵,每个位置上的数字是占有这个位置的图形的编号,中间没有空格。例如上面A、B、C、D的编号依次是1、2、3、4,那么就可以输出 1112 1412 3422 3442 每种方案之间以一个空行相隔
--------------------------------------------------------------------------------------------------------------
思路:
产生四个图块(二维数组),存储相应的块,初始化图形:
为4*4的矩形,初始化为0;
对每一行进行循环,对每一列进行循环;
判别此处的图形有未被填充,
若已经填充,则跳到同一行的下一列继续判别(若以达到最末列,则跳转到下一行);
若未填充,则从未使用的图形块中选择一块;
首先,我们已经知道图形(4*4)上,该列的位置上未有放置,则遍历我们选中的子块;
当然:首先我们需要判别:这个块能否填充这个位置并且未越过界(包括上下界以及左右界)
则(首要条件)
i+block.i-1<=4
即:未越过下边界;
那么: 行约束(下界)已经得到满足;
因此,我们尝试在每一行中循环进行判别;
我们首先在子图块的第一行(即4*4图中的i行)开始遍历:
for(row=i;row<=i+block.i-1;row++)
继而,我们必须寻找到第一行的第一个非零元(这样我们就能判断出子块的位置了,记住这一点)
一旦确定之后,那么剩下的工作的就是:判断每一行有无越界情况,若有,则跳出循环
选取下一个子块尝试填充
若无越界,那么对该行元素尝试填充,一旦发现该填充中非零元与图块中的非零元冲突,则亦不满足条件,跳出
重新选择一个子块填充。
--------------------------------------------------------------------------------
代码:
#include<iostream>
using namespace std;
#define TRUE 1
#define FALSE 0
// data definition:
int n =4;
// int tab[4][2] = {{2,3},{4,2},{2,1},{3,2}};
// int dat[22] = {1,1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,1,0,1,0,1,1};
int tab[4][2] = {{2,3},{4,2},{2,1},{4,2}};
int dat[24] = {1,0,1,1,0,1,0,1,0,1,1,1,0,1,1,1,1,0,1,0,1,0,1,1};
struct Block{
int row;
int col;
int **ptr;
};
struct Order{
int ord;
Order* next;
};
Order* order = new Order;
Block blk[4];
int Nsolver =0;
void init(){//初始化函数;
int **tmp ;
int offset =0;
for(int ib =0;ib<n;ib++){
blk[ib].row =tab[ib][0]; blk[ib].col =tab[ib][1];
tmp = new int* [blk[ib].row];
for(int irow=0;irow<blk[ib].row;irow++){
tmp[irow] = new int[blk[ib].col];
for(int icol=0;icol<blk[ib].col;icol++){
tmp[irow][icol] = dat[offset+(irow)*blk[ib].col+icol];
}
}
offset +=blk[ib].row*blk[ib].col;
blk[ib].ptr = tmp;
}
tmp =NULL;
cout<<endl<<" ---> 初始化图块成功!"<<endl;
}
void disp(int ib){//验证子块无误函数;
cout<<"子块"<<ib<<endl<<blk[ib].row<<" "<<blk[ib].col;
for(int irow=0;irow<blk[ib].row;irow++){
cout<<endl;
for(int icol=0;icol<blk[ib].col;icol++)
cout<<blk[ib].ptr[irow][icol]<<" ";
}
cout<<endl;
}
// ----------------- ORDER 处理函数 --------------- //
void pop(){//
if(order){// if not NULL
//delete header;
order =order->next;
}
else cout<<"POP FAILED!"<<endl;
}
void push(int odr){//
Order *tmp = new Order;
tmp->ord = odr;
tmp->next = order;
order = tmp;
}
int size(Order* head){
int length=0;
while(head){
length++;
head = head->next;
}
return length;
}
int unctn(Order* head ,int ib){
while(head){
if(head->ord==ib){cout<<"子块"<<ib<<"已在!"<<endl; return 0;}
head = head->next;
}
return 1;
}
void dispOrder(Order* head){
cout<<"ORDER: ";
while(head){
cout<<head->ord<<" ";
head = head->next;
}
}
//--------------------- 图处理函数 ---------------------//
bool fullMap(int** map){
for(int r=0;r<n;r++)
for(int c=0;c<n;c++)
if (map
[c]==0) return FALSE;
return TRUE;
}
int** copyMap(int** map){
cout<<"复制图中...."<<endl;
int** locmap;
locmap = new int* [n];
for(int i=0;i<n;i++){
locmap[i]=new int[n];
for(int j=0;j<n;j++)
locmap[i][j] = map[i][j];
}
return locmap;
}
void dispMap(int** map){
cout<<"-------< MAP >------"<<endl;
for(int r=0;r<n;r++){
cout<<endl;
for(int c=0;c<n;c++)
cout<<map
[c]<<" ";
}
cout<<endl;
}
bool chkBlk(int** map ,int ib){
//cout<<"将要检验子块"<<ib<<"是否可以填充图..."<<endl;
int Row,Col,flag=0;
for(int r=0;r<n;r++){
for(int c=0;c<n;c++){
if (map
[c]==0){//此处可以填充,在此开始填充;
Row = r; Col = c;
flag=1;
cout<<Row<<"行 "<<Col<<"列可以被填充"<<endl;
break;
}
}
if(flag) break;
}
if(flag==0) cout<<"出错!已经找不到可以被填充的位了!"<<endl;
int Ncol;//第一行之非空之列数;
if (blk[ib].row+Row <=n){ //下界条件;
for(int col=0;col<blk[ib].col;col++){ //确定子块在图中位置;
if (blk[ib].ptr[0][col]) {Ncol = col;break;}
}//
disp(ib);
//cout<<"第一行非零元的列数为Ncol:"<<Ncol<<endl;
//cout<<"右界条件(要小于4)Col+(blk[ib].col-Ncol)-1:"<<Col+(blk[ib].col-Ncol)-1<<endl;
//cout<<"左界条件(要不小于0):Col-Ncol"<<Col-Ncol<<endl;
//cout<<(Col+(blk[ib].col-Ncol)-1<4)<<(Col-Ncol>=0)<<endl;
if((Col+(blk[ib].col-Ncol)-1<4)&&(Col-Ncol>=0)){//未越过左右界;
cout<<"满足左右界条件"<<endl;
int icol = Col;
//cout<<"可以排入的列号为(图):"<<Col<<endl;
//dispMap(map);
for(int col=Ncol;col<blk[ib].col;col++){//第一行的判别
if(map[Row][icol]&&blk[ib].ptr[0][col])//冲突的判断;
{
cout<<"第一行有冲突!"<<endl;
return FALSE;
}
else icol++;
}//第一行
for(int row=1;row <blk[ib].row;row++){
icol = Col-Ncol;
//cout<<"icol="<<icol<<endl;
for(int col=0;col<blk[ib].col;col++)
if(map[Row+row][icol]&&blk[ib].ptr[row][col]) {
cout<<"图:"<<map[Row+row][icol]<<" 子块"<<blk[ib].ptr[row][col]<<endl;
cout<<"第"<<Row+row<<"行有冲突!"<<endl;return FALSE;
}
else icol++;
}//其他行
//------------------------> 开始填充 <--------------------------//
cout<<"开始填充...."<<endl;
icol = Col; //cout<<"icol="<<icol<<endl;
for(int col=Ncol;col <blk[ib].col;col++,icol++)//第一行的填充;
if((map[Row][icol]==0)&&(blk[ib].ptr[0][col])){//如果此处为空,并且子块非空;
map[Row][icol] = ib+1;//cout<<"icol="<<icol<<endl;
}
//cout<<"第一行的填充结果:"<<endl;
//dispMap(map);
for(int row=1;row <blk[ib].row;row++){//其余行的填充;
icol = Col-Ncol;
for(int col=0;col <blk[ib].col;col++,icol++)
if((map[Row+row][icol]==0)&&(blk[ib].ptr[row][col]))//如果此处为空;
map[Row+row][icol] =ib+1;
//cout<<"第"<<Row+row<<"行的填充结果:"<<endl;
//dispMap(map);
}//填充完毕;
cout<< "子块"<<ib<<"填充完毕!"<<endl<<"当前图为:"<<endl;
dispMap(map);
return TRUE;
}//左右界
else {cout<<"子块"<<ib<<"不满足左右界条件"<<endl ;return FALSE;}
}//下界条件
else {cout<<"子块"<<ib<<"不满足下界条件"<<endl ;return FALSE;}
}// void
// ------> 主递归函数;
bool chsOrder(int** mapPre){
int** locMap;
if (size(order)==n+1){ //已经排完四个子块了;
//cout<<"已经排完四个子块了!"<<endl;
if (fullMap(mapPre)) {
dispMap(mapPre);
cout<<"--------------------"<<endl;
cout<<"太帅了!你得到第"<<++Nsolver<<"个解"<<endl;
getchar();return TRUE;}
else return FALSE;
}
else{ //还没使用完所有子块,故选择下一个满足情况之子块;
int ib =0;
while(ib<4){
//getchar();
if (unctn(order ,ib)){
//cout<<"子块"<<ib<<"未被使用!"<<endl;
locMap = copyMap(mapPre);//接受上次决策后的新图;
if (chkBlk(locMap,ib)){//满足填充之条件,满足则就会填充;
//cout<<"满足条件,将子块"<<ib<<"弹入栈中!"<<endl;
push(ib);
//dispOrder(order);
chsOrder(locMap);//此子块可以填充,递归选择下一子块;
//cout<<"此路不可! 弹出"<<endl;
//dispMap(mapPre);
//cout<<"-----------"<<endl;
//dispMap(locMap);
//getchar();
pop();
}
}
ib++;
}// while ib
//cout<<"hehe ,怎么也找不到了是吧!"<<endl;
}//if not used all
}//func
int main(){
cout<<" --------------> 拼图游戏的解法 ";
int** map;
map = new int*[n];
for(int i=0;i<n;i++){
map[i] = new int[n];
for(int j=0;j<n;j++)
map[i][j] =0;
}
order->ord =n;
order->next = NULL;
// initialize
init();
//disp();
// choose the order
chsOrder(map);
//
cout<<endl<<endl<<"---------> 求解结束!";
getchar();
}
后记: 在两年之后的今天,想不到我认识了两位真正的acmer,跟他们的讨论让我觉得这篇日志实在是雕虫小技。拼图游戏其实是dfs方法,当然下面的代码问题在于没有合理的应用状态表示,所以结构显得非常的臃肿。例如拼图完全可以使用二进制位图表示等等,不必显式的使用栈,等等。现在临毕业,时间所限,不再更改了,纯属纪念。
2011-3-24