题解 P1312 【Mayan游戏】

· · 题解

先读懂题。题目要求模拟一个游戏的运行,并且搜索出最优解。不能dp也没啥启发式操作显然就是爆搜啊。

模拟和搜索难度不大,但模拟有很多细节。我调了好久才a掉这题。

有oop经验的我十分自然地把模拟和搜索分开,即用一个struct封装状态,这样代码能清晰不少,调试也方便。我感觉比那一大堆裸露的函数、数组复制好多了。

模拟

定义struct puzzle,要实现3个操作:移动方块,消除,检查状态。

使表示自然一些,这里我用i表示列,j表示行

为了方便操作,用数组h[]表示每列的高度,这样能减少循环的次数。

1.移动方块

需要讨论移到的位置是否为空。如果为空,将被移的方块放在移到列的顶部,处理掉落(整体往下移),然后改变2列的高度。如果非空,直接交换颜色值。

2.消除

因为是所有满足条件的方块同时消,而且3连情况复杂,这里我是开一个bool数组mark一下要消的方块。2个双重循环,分别检查横/竖方向是否有3个颜色相同,如果相同就标记一下(合并成1个在效率方便没啥提升)。最后按列,把没标记的重新堆起来,消除的要清0,记得改变高度。

重复直到不发生消除。

3.检查全清

很简单,h[0]~h[4]全为0就是全清。考虑到循环代价我没用循环。

搜索

按照题意的字典序搜就行了,没什么好说的。看清楚点,右移优先于左移。枚举要移动的方块和方向。移动操作记录在函数外的数组里,不断更新就行。但既然是爆搜,显然要剪枝。

交换两个颜色相同的方块没有意义,一般都能想到。

然后应该能注意到吧,如果把2个方块来回交换而没有发生消除,那么移动没有意义,可以剪掉(稍微复杂,但后面后更优的剪法)

细想一下,来回移动的实质是交换,而又因为是交换,所以让左边的右移等价于右边的左移,所以搜了两遍。前者比较优先,所以只需搜前者的右移。当且仅当坐标为空时才左移。这样剪是ac的关键。

register卡常是有用的,效果比较明显,但不是关键

我瞎搞就搞出524ms了,进榜第3页……

感觉color剪枝没啥效果,毕竟检查也是有代价的(我加了根本没明显提升)

其他细节

读入注意一下,如果状态数组正好是5*7大小,读入不要越界,不然会死,而且可能还不知道是怎么死的(我死在#8)

如果没有检查h是否合法,一定要确保空的格子清零清零清零!!!!

可以参考一下我的代码风格(注释基本上是在写程序时很自然地加的)

#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
int n;
int his[6][3];
struct puzzle {
    int a[5][7],h[5]; //颜色数据和高度
    void moveblock(int x,int y,int z) {
        if (h[x+z]<=y) { //对应的位置为空
            a[x+z][h[x+z]++]=a[x][y];
            for (register int j=y+1;j<h[x];j++) {
                a[x][j-1]=a[x][j];
            }
            h[x]--;
            a[x][h[x]]=0;
        } else { //对应位置有方块 直接交换
            a[x+z][y]^=a[x][y];
            a[x][y]^=a[x+z][y];
            a[x+z][y]^=a[x][y];
        }
    }
    bool process() { //进行消除,返回是否发生消除
        bool res=false;
        bool mark[5][7]={};
        //纵向消除
        for (register int i=0;i<5;i++) {
            for (register int j=1;j<h[i]-1;j++) {
                if (a[i][j-1]==a[i][j] && a[i][j]==a[i][j+1]) {
                    res=mark[i][j-1]=mark[i][j]=mark[i][j+1]=true;
                }
            }
        }
        //横向消除
        for (register int i=1;i<4;i++) {
            for (register int j=0;j<h[i];j++) {
                if (a[i-1][j]==a[i][j] && a[i][j]==a[i+1][j]) {
                    res=mark[i-1][j]=mark[i][j]=mark[i+1][j]=true;
                }
            }
        }
        if (!res) return false; //没发生消除就不处理掉落了
        //处理掉落
        for (register int i=0;i<5;i++) {
            int k=0; //掉落的位置
            for (register int j=0;j<h[i];j++) {
                if (!mark[i][j]) {
                    a[i][k++]=a[i][j];
                    if (k-1!=j) a[i][j]=0; //不是同一格
                } else {
                    a[i][j]=0; //被消除的要清成0
                }
            }
            h[i]=k;
        }
        return true;
    }
    inline bool allclear() {
        return !(h[0] || h[1] || h[2] || h[3] || h[4]);
    }
    void process_ex() {
        while (process());
    }
};
puzzle ini;
void dfs(puzzle cur,int t) { //当前状态,已移动次数
    bool clear=cur.allclear();
    if (t==n) {
        if (clear) { //达成
            for (int i=0;i<n;i++) {
                printf("%d %d %d\n",his[i][0],his[i][1],his[i][2]);
            }
            exit(0); //强退
        }
        return; //失败
    }
    for (register int i=0;i<5;i++) { //枚举x
        for (register int j=0;j<cur.h[i];j++) { //枚举y
            if (i!=4) { //可以往右移
                if (cur.a[i+1][j]!=cur.a[i][j]) {
                    puzzle nxt=cur;
                    nxt.moveblock(i,j,1); //移动
                    his[t][0]=i, his[t][1]=j, his[t][2]=1; //历史记录
                    nxt.process_ex(); //处理消除
                    dfs(nxt,t+1);
                }
            }
            if (i!=0) { //可以往左移
                if (!cur.a[i-1][j]) { //剪枝:只有左边为0才能往左移
                    puzzle nxt=cur;
                    nxt.moveblock(i,j,-1);
                    his[t][0]=i, his[t][1]=j, his[t][2]=-1;
                    nxt.process_ex();
                    dfs(nxt,t+1);
                }
            }
        }
    }
}
int main() {
    scanf("%d",&n);
    for (int i=0;i<5;i++) {
        ini.h[i]=0;
        for (int &j=ini.h[i];true;j++) {
            int tmp;
            scanf("%d",&tmp);
            if (tmp==0) break;
            if (j<7) ini.a[i][j]=tmp;
        }
    }
    dfs(ini,0);
    printf("%d\n",-1);
    return 0;
}