AcWing 185. 玛雅游戏 (dfs + 剪枝 + 模拟)

 

首先,我们考虑搜索顺序,我们从第一列的第一格搜起,搜完当前列的方格的方格后,然后搜下一列,对于一个方格,我们优先右移,再搜左移.这样可以保证搜出来的字典序最小(因为先搜到的合法答案的字典序一定是最小的)

由于时间复杂度大概是\Theta (2*35^5),超时了,所以我们考虑剪枝:

1.如果一种颜色的方块只有1个或者2两个,那么就回溯

2.如果一个方块的左边有方块,则不要执行左移操作,因为这样的字典序一定不如右边的方块左移

3.一个错误的剪枝:如果左右方块一致,那么就不移动.原因是:题目要求步数要为n,有可能每次我们只走合法的路线,步数不足n次.

 代码如下:

#include <bits/stdc++.h>
// #define LOCAL
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(false), cin.tie(0)
#define int long long
#define debug(a) cerr << #a << "=" << a << endl;
using namespace std;
int n;
int cnt[12], g[5][7];
int bcnt[5][12], bg[5][5][7];
struct rec_{
    int x, y, d;
}path[6];
bool st[5][7];

void mv(int a, int b, int c){
    swap(g[a][b], g[c][b]);

    for (;;){
        bool is_change = false;
        //先使用双指针处理每一列悬空的方块
        for (int x = 0; x < 5; ++x){
            int z = 0;
            for (int y = 0; y < 7; ++y)
                if (g[x][y])
                    g[x][z++] = g[x][y];
            while (z < 7)
                g[x][z++] = 0;
        }

        //这里使用了类似延迟删除的技巧,将所有符合删除条件的方块打上标记,后面再一起删除
        memset(st, 0, sizeof st);
        for (int x = 0; x < 5; ++x)
            for (int y = 0; y < 7; ++y){
                if (g[x][y] == 0)
                    continue;
                int l = x, r = x;
                while (l - 1 >= 0 && g[l - 1][y] == g[x][y])
                    --l;
                while (r + 1 < 5 && g[r + 1][y] == g[x][y])
                    ++r;
                if (r - l + 1 >= 3){
                    st[x][y] = true;
                    is_change = true;
                }else{
                    l = r = y;
                    while (l - 1 >= 0 && g[x][l - 1] == g[x][y])
                        --l;
                    while (r + 1 < 7 && g[x][r + 1] == g[x][y])
                        ++r;
                    if (r - l + 1 >= 3){
                        st[x][y] = true;
                        is_change = true;
                    }
                }
            }
        if (!is_change)
            break;
        for (int x = 0; x < 5; ++x)
            for (int y = 0; y < 7; ++y)
                if (st[x][y]){
                    --cnt[0];
                    --cnt[g[x][y]];
                    g[x][y] = 0;
                }
    }
}

bool dfs(int u){
    if (u == n)
        return !cnt[0];
    //剪枝1:如果某个颜色的方块只有一个或者两个,那么就直接回溯
    for (int i = 0; i <= 10; ++i)
        if (cnt[i] == 1 || cnt[i] == 2)
            return false;
    //进行备份
    memcpy(bg[u], g, sizeof g);
    memcpy(bcnt[u], cnt, sizeof cnt);
    for (int x = 0; x < 5; ++x)
        for (int y = 0; y < 7; ++y){
            if (g[x][y] == 0)
                continue;
            int nx = x + 1;//先右移
            if (x + 1 < 5){
                path[u] = {x, y, 1};
                mv(x, y, nx);
                if (dfs(u + 1))
                    return true;
                memcpy(g, bg[u], sizeof bg[u]);
                memcpy(cnt, bcnt[u], sizeof bcnt[u]);
            }

            nx = x - 1;//再左移,注意剪枝2:如果左边有方块,则不左移,因为左边的方块右移一定会比右边的方块左移字典序小
            if (x - 1 >= 0 && g[nx][y] == 0){
                path[u] = {x, y, -1};
                mv(x, y, nx);
                if (dfs(u + 1))
                    return true;
                memcpy(g, bg[u], sizeof bg[u]);
                memcpy(cnt, bcnt[u], sizeof bcnt[u]);
            }
        }

    return false;

}

signed main(){
#ifdef LOCAL
    freopen("input.in", "r", stdin);
    freopen("output.out", "w", stdout);
#endif
    IOS;
    cin >> n;
    for (int i = 0; i < 5; ++i){
        int x = 0, t;
        while (cin >> t, t){
            g[i][x++] = t;
            ++cnt[t];
            ++cnt[0];
        }
    }

    if (dfs(0))
        for (int i = 0; i < n; ++i)
            cout << path[i].x << " " << path[i].y << " " << path[i].d << "\n";
    else
        cout << -1 << "\n";
    return 0;
}

这里再额外提一嘴,这道题的模拟也是有难度的,对要删除的方块打上标记,延迟删除.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值