智慧搜索&智慧模拟的好题。
核心:两个函数(消去函数,掉落函数)和一个最优性剪枝。
具体细节见代码注释。
Code:
#include<cstdio>
#include<iostream>
#include<algorithm>
#define ri register int
using namespace std;
const int MAXN=10;
int n,ss[MAXN][MAXN],book[MAXN][MAXN];
//ss;记录地图某一位置格子的序号
int plx[MAXN],ply[MAXN],plf[MAXN];
//plx,ply,plf:记录状态
int nx[3]={1,1,-1},
ny[3]={1,0,0};
void swp(int x1,int y1,int x2,int y2,int step)//方格移位函数
{
int mid=ss[x1][y1];
ss[x1][y1]=ss[x2][y2],ss[x2][y2]=mid;
}
void fall()//下落函数
{
int fst[5];
for(ri i=4;i>=0;i--) fst[i]=0;//fst记录某一竖列顶端方块的纵坐标
for(ri i=0;i<=4;i++)
for(ri j=6;j>=0;j--)
if(ss[i][j]>0) { fst[i]=j; break; }
for(ri i=0;i<=4;i++)
for(ri j=fst[i]-1;j>=0;j--)
{
if(ss[i][j]==0)
{
fst[i]--;
for(int k=j;k<=fst[i];k++) ss[i][k]=ss[i][k+1];
ss[i][fst[i]+1]=0;//一定别忘了清空ss数组(原fst[i]位置的数,否则影响接下来的游戏状态(即ss数组))
}
}
}
bool xiaoqu()//消去函数
//判断整个状态中是否存在可消去的地方,只需枚举所有坐标,
//判断以当前坐标为左端点的横条(以样例图为参考系)和以当前坐标为下端点的竖条是否可以直接消去
//这样显然可以保证覆盖到所有情况(所有可被消去的方块连条的某个端点必然被枚举到)
{
for(ri i=0;i<=4;i++)
for(ri j=0;j<=6;j++) book[i][j]=0;
for(ri i=0;i<=4;i++)
for(ri j=0;j<=6;j++)
{
if(ss[i][j]==0) continue;
int len=0;
for(ri k=i;k<=4;k++)//消去竖条(以给定样例为参考系)
{
if(ss[i][j]==ss[k][j]) len++;
else break;
}
if(len>=3)
for(int k=i;k<=i+len-1;k++) book[k][j]=1;
len=0;//注意清空len
for(ri k=j;k<=6;k++)//消去横条(以给定样例为参考系)
{
if(ss[i][j]==ss[i][k]) len++;
else break;
}
if(len>=3)
for(ri k=j;k<=j+len-1;k++) book[i][k]=1;
}
//因为存在
bool flag=0;
for(ri i=0;i<=4;i++)
for(ri j=0;j<=6;j++)
if(book[i][j]==1) { ss[i][j]=0; flag=1; }
return flag;
}
bool check()//检查当前所有方块是否被消除完毕
{
for(ri i=0;i<=4;i++)
for(ri j=0;j<=6;j++)
if(ss[i][j]>0) return 0;
return 1;
}
void dfs(int step)//寻找可行性解函数
{
if(step==n+1)//已到达了规定步数
{
if(check())
//由于保证方案是按照字典序枚举,故有合法的方案直接输出即可。
{
for(ri i=1;i<=n;i++) cout<<plx[i]<<" "<<ply[i]<<" "<<plf[i]<<'\n';
exit(0);
}
return;
}
for(ri x=0;x<=4;x++)
for(ri y=0;y<=6;y++)
for(ri k=1;k<=2;k++)//由于输出字典序最小的方案,所以坐标数值从小到大枚举,移动方向优先右移
{
int tx=x+nx[k],ty=y+ny[k];
if(tx<0||tx>4||ty<0||ty>6) continue;//移动目的地越界
if(ss[x][y]==0) continue;//当前坐标无方块
//if(ss[x][y]==ss[tx][ty]) continue;//剪枝1:相同颜色的方块交换位置无意义
//但是容易被hack(见文件夹中的hack数据)
if(k==2&&ss[tx][ty]>0) continue;//剪枝2:左移时有方块的情况与该方块左边的方块左移的情况重复,故剪枝
//if(k==1&&ss[tx][ty]>0) continue;//剪枝2的等价:右移时有方块的情况与该方块右边的方块左移的情况重复,故剪枝
//但由于要求输出字典序最小的,故选择k=0的情况(这两个剪枝不能同时使,否则两种交换方式都是不合法的)
plx[step]=x,ply[step]=y;
if(k==1) plf[step]=1;
if(k==2) plf[step]=-1;
int tmp[10][10];
for(ri i=0;i<=4;i++)
for(ri j=0;j<=6;j++) tmp[i][j]=ss[i][j];//tmp:保存ss的原状态,方便回溯
swp(x,y,tx,ty,step);
fall();//防止单个方块和0交换的情况
bool flag=xiaoqu();
fall();
while(flag)
{
flag=xiaoqu();
fall();//消去后记得判下落
}//注意:在方块左移or右移后,如果存在消去情况,它会一直执行消去操作,直到无消去状态为止。这算在一步内。
//(脑补一下平常玩的消消乐)
dfs(step+1);
for(ri i=0;i<=4;i++)
for(ri j=0;j<=6;j++) ss[i][j]=tmp[i][j];//状态回溯
}
}
int main()
{
scanf("%d",&n);
for(ri i=0;i<=4;i++)
{
for(ri j=0;j<=7;j++)
//当某一竖列是满的时(7个格子全有块),该行会输入8个整数(7个正整数和一个用于结束的0)
//所以输入的第二层的循环范围是0-7而非0-6
{
scanf("%d",&ss[i][j]);
if(ss[i][j]==0) break;
}
}
dfs(1);
cout<<"-1";
return 0;
}

本文介绍了一种结合智慧搜索和智慧模拟的算法题解决方案,核心包括消去函数、掉落函数和最优性剪枝策略。通过C++代码实现,详细解释了如何通过方格移位、下落、消去及检查等函数完成游戏状态更新,最终寻找字典序最小的可行性解。
961

被折叠的 条评论
为什么被折叠?



