23. 带旋转的数独游戏

数独是一个基于逻辑的组合数字放置拼图,在世界各地都很受欢迎。 在这个问题上,让我们关注 16 * 16 网格的拼图,其中包含 4 * 4 个区域。 目标是用十六进制数字填充整个网格,即 0123456789 A B C D E F,以便每列,每行和每个区域包含所有十六进制数字。 下图显示了一个被成功解决的数独例子:

 

昨天,周老师解决了一个数独并将其留在桌面上。 然而,龙龙想和他开个玩笑——龙龙打算对这个已经解决的数独进行多次以下操作。 选择一个  4 *4 的小区域并顺时针旋转 90 度。 周老师回来发现他拼好的数独板被打乱了,开始挠头,你能帮他以最小的步数恢复原样吗?请你手把手的教他怎么做,也就是需要输出方案。 请注意选择要旋转的方块不能跨越任何小区域,也就是说必须选择一块完整的小区域旋转。小区域的定义在上面,16 * 16 的网格被分成 4 * 4 个小区域。 Input 第一行输入一个正整数 T 空格 左括号 1 小於等於 T 小於等於 10 立方 右括号 表示数据组数; 接下来每组数据输入一个 16 * 16 的数独图,表示被龙龙打乱后的数独面板。 Output 对于每组数据: 第一行输出一个整数 a n s ,表示周老师最少需要逆时针旋转多少次才能恢复原样。 接下来输出 a n s 行,每行两个数pxpy,表示逆时针旋转一次第px行第py列的小矩阵。

解题思路:

本题爆搜时间复杂度较高,需要进行大量剪枝才行。

我们先写好需要用的函数,之后方便调用

  1. 获得值函数(将a~e转换为10~15,方便存储)
  2. 旋转函数
  3. 检查函数(每次检查刚放进来的块,如果满足数独性质,则返回1,并且将块中元素加入检查函数的检查队列中,用于下一次放块后的检查)
  4. 回溯函数(若之后放块都不能满足要求,说明这个块放在这里是错的,我们就要将检查函数的检查队列中这个块的数据删除,便于下一次能正确检查)
  5. dfs从左到右从上到下dfs
  6. 记录数据
  7. 回溯数据

经巨佬提醒我们发现旋转函数的复杂度还是高,我们可以预处理每一个小块旋转后的样子,这样的话还可以减少时间//不过不需要这个也行

代码:

#include<bits/stdc++.h>  
using namespace std;  
int a[30][30];   
int ck1[22][22];  
int ck2[22][22];  
struct Node  
{  
    int x, y, t;  
};  
int ans;  
vector<Node> as, fas;  
int get(char ch){  
    if (ch == '0')return 10;  
    else if (ch > '0' && ch <= '9')return ch - '0';  
    else return ch - 'A' + 11;  
}  
void roat(int x ,int y ){   
  
    int xx=(x)*4;  //块之间的映射1~4 --> 1~16
    int yy=(y)*4;   
    int g[5][5];   
    for(int i=xx;i<=xx+3;i++){   
        for(int j=yy;j<=yy+3;j++){   
            g[i-xx][j-yy]=a[i][j];   
        }   
    }   
    for(int i=xx;i<=xx+3;i++){   
        for(int j=yy;j<=yy+3;j++){   
            a[i][j]=g[j-yy][3+xx-i];   
        }   
    }   
      
}   
bool check(int aa,int bb){   
    aa=aa*4;   
    bb=bb*4;   
        for(int i=aa;i<=aa+3;i++)  
        for(int j=bb;j<=bb+3;j++){   
            int num=a[i][j];  
             if(ck1[i][num]||ck2[j][num]){  
                 return 0;  
            }  
        }   
     for(int i=aa;i<=aa+3;i++)  
        for(int j=bb;j<=bb+3;j++){   
            int num=a[i][j];  
            ck1[i][num]=1,ck2[j][num]=1;  
            }  
    return 1;   
}   
void uncheck(int aa,int bb){  
    aa=aa*4;   
    bb=bb*4;   
    for(int i=aa;i<=aa+3;i++)  
        for(int j=bb;j<=bb+3;j++){   
            int num=a[i][j];  
            ck1[i][num]=0,ck2[j][num]=0;  
            }  
}  
void dfs(int x, int y, int t) {  
    if (x == 4) {  
        if (t < ans) {  
            ans = t;  
            fas = as;  
        }  
        return;  
    }  
    for (int i = 0; i < 4; i++) {  
        if (!check(x,y)) {  
            roat(x, y);  
            continue;  
        }  
        as.push_back(Node{ x+1,y+1,i });  
        if (y == 3) {  
            dfs(x + 1, 0, t + i);  
        }  
        else dfs(x, y + 1, t + i);  
        as.pop_back();  
        uncheck(x,y);  
        roat(x,y);  
    }  
}  
int main() {  
    ios::sync_with_stdio(false);  
    cin.tie(0);   
    cout.tie(0);  
    int T;  
    cin >> T;  
    while (T--) {  
        ans = 0x3f3f3f3f;  
        as.clear();  
        fas.clear();  
        memset(ck1, 0, sizeof ck1);    
        memset(ck2, 0, sizeof ck2);    
          
        for(int i=0;i<16;i++)for(int j=0;j<16;j++){   
            char x;   
            cin>>x;   
            int gx=get(x);  
            a[i][j]=gx;   
        }   
          
        dfs(0, 0, 0);  
        cout << ans << endl;  
        for (auto &x : fas) {  
            for (int i = 0; i < x.t; i++) {  
                cout << x.x  << " " << x.y <<endl;  
            }  
        }  
    }  
    return 0;  
}  
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值