AcWing 169. 数独2(复杂的搜索+剪枝)

在这里插入图片描述

思路:

  • 可以看出来,这就是数独加强版, 9 ∗ 9 9*9 99变成了 16 ∗ 16 16*16 1616。对应的算法效率要求也更高。一开始想在 9 ∗ 9 9*9 99的代码上加剪枝水过去,搜索功力还是不足过不去。。。
  • 参考了《进阶指南》的思路和代码。
  • 思路就是搜索的时候遍历每个点,如果这个点不能选数了,那么剪掉,如果只能选一个数,那就选上。(这里以及下面所有“点”都是针对空格点)
  • 遍历每一行,如果这一行没数可选那就剪掉,如果只能选一个数那就选上。列和每个4*4小块同理。
  • 最后再按照常规思路,遍历每个格子选出可选数最少的格子从这里开始枚举选哪个数搜索下去。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
char s[16][16];
int cnt[1 << 16],f[1 << 16],num[16][16];
vector<int>e[1 << 16];
void work(int i,int j,int k) {
    for(int t = 0;t < 16;t++) {
        num[i][t] &= ~(1 << k);
        num[t][j] &= ~(1 << k);
    }
    int x = i / 4 * 4,y = j / 4 * 4;
    for(int ti = 0;ti < 4;ti++) {
        for(int tj = 0;tj < 4;tj++) {
            num[x + ti][y + tj] &= ~(1 << k);
        }
    }
}
bool dfs(int ans) {
    if(!ans) return true;
    int pre[16][16];
    memcpy(pre,num,sizeof(num));
    //遍历所有格子
    for(int i = 0;i < 16;i++) {
        for(int j = 0;j < 16;j++) {
            if(s[i][j] == '-') {
                if(!num[i][j]) return false; //有格子选不了直接剪掉
                if(cnt[num[i][j]] == 1) { //只能选这一个数,选上
                    s[i][j] = f[num[i][j]] + 'A';
                    work(i,j,f[num[i][j]]);
                    if(dfs(ans - 1)) return true;
                    s[i][j] = '-';
                    memcpy(num,pre,sizeof(num));
                    return false;
                }
            }
        }
    }
    
    //遍历每一行
    for(int i = 0;i < 16;i++) {
        int w[16],o = 0;
        memset(w,0,sizeof(w));
        for(int j = 0;j < 16;j++) {
            if(s[i][j] == '-') {
                o |= num[i][j];
                for(int k = 0;k < e[num[i][j]].size();k++) {
                    ++w[e[num[i][j]][k]];
                }
            } else {
                o |= (1 << (s[i][j] - 'A'));
                w[f[1<<(s[i][j] - 'A')]] = -1;
            }
        }
        if(o != (1 << 16) - 1) return false;
        for(int k = 0;k < 16;k++) {
            if(w[k] == 1) {
                for(int j = 0;j < 16;j++) {
                    if(s[i][j] == '-' && ((num[i][j] >> k) & 1)) {
                        s[i][j] = k + 'A';
                        work(i,j,k);
                        if(dfs(ans - 1)) return true;
                        s[i][j] = '-';
                        memcpy(num,pre,sizeof(num));
                        return false;
                    }
                }
            }
        }
    }
    
    //遍历每一列
    for(int j = 0;j < 16;j++) {
        int w[16],o = 0;
        memset(w,0,sizeof(w));
        for(int i = 0;i < 16;i++) {
            if(s[i][j] == '-') {
                o |= num[i][j];
                for(int k = 0;k < e[num[i][j]].size();k++) {
                    ++w[e[num[i][j]][k]];
                }
            } else {
                o |= (1 << (s[i][j] - 'A'));
                w[f[1<<(s[i][j] - 'A')]] = -1;
            }
        }
        if(o != (1 << 16) - 1) return false;
        for(int k = 0;k < 16;k++) {
            if(w[k] == 1) {
                for(int i = 0;i < 16;i++) {
                    if(s[i][j] == '-' && ((num[i][j] >> k) & 1)) {
                        s[i][j] = k + 'A';
                        work(i,j,k);
                        if(dfs(ans - 1)) return true;
                        s[i][j] = '-';
                        memcpy(num,pre,sizeof(num));
                        return false;
                    }
                }
            }
        }
    }
    
    //遍历每一个4*4小块
    for(int i = 0;i < 16;i += 4) {
        for(int j = 0;j < 16;j += 4) {
            int w[16],o = 0;
            memset(w,0,sizeof(w));
            for(int p = 0;p < 4;p++) {
                for(int q = 0;q < 4;q++) {
                    if(s[i + p][j + q] == '-') {
                        o |= num[i + p][j + q];
                        for(int k = 0;k < e[num[i + p][j + q]].size();k++) {
                            ++w[e[num[i + p][j + q]][k]];
                        }
                    } else {
                        o |= (1 << (s[i + p][j + q] - 'A'));
                        w[f[1 << (s[i + p][j + q] - 'A')]] = -1;
                    }
                }
            }
            if(o != (1 << 16) - 1) return false;
            for(int k = 0;k < 16;k++) {
                if(w[k] == 1) {
                    for(int p = 0;p < 4;p++) {
                        for(int q = 0;q < 4;q++) {
                            if(s[i + p][j + q] == '-' && ((num[i + p][j + q] >> k) & 1)) {
                                s[i + p][j + q] = k + 'A';
                                work(i + p,j + q,k);
                                if(dfs(ans - 1)) return true;
                                s[i + p][j + q] = '-';
                                memcpy(num,pre,sizeof(num));
                                return false;
                            }
                        }
                    }
                }
            }
        }
    }
    
    //选择可选数最少的那个格子
    int k = 17,tx,ty;
    for(int i = 0;i < 16;i++) {
        for(int j = 0;j < 16;j++) {
            if(s[i][j] == '-' && cnt[num[i][j]] < k) {
                k = cnt[num[i][j]];
                tx = i;
                ty = j;
            }
        }
    }
    for(int i = 0;i < e[num[tx][ty]].size();i++) {
        int tz = e[num[tx][ty]][i];
        work(tx,ty,tz);
        s[tx][ty] = tz + 'A';
        if(dfs(ans - 1)) return true;
        s[tx][ty] = '-';
        memcpy(num,pre,sizeof(num));
    }
    return false;
}
void solve() {
    for(int i = 1;i < 16;i++) scanf("%s",s[i]);
    for(int i = 0;i < 16;i++) {
        for(int j = 0;j < 16;j++) {
            num[i][j] = (1 << 16) - 1;
        }
    }
    int ans = 0;
    for(int i = 0;i < 16;i++) {
        for(int j = 0;j < 16;j++) {
            if(s[i][j] != '-') {
                work(i,j,s[i][j] - 'A');
            } else {
                ans++;
            }
        }
    }
    if(dfs(ans)) {
        for(int i = 0;i < 16;i++) {
            for(int j = 0;j < 16;j++) {
                printf("%c",s[i][j]);
            }
            printf("\n");
        }
    } else {
        printf("DEBUG\n");
    }
    printf("\n");
}
int get_cnt(int i) {
    int k = i & -i;
    e[i].push_back(f[k]);
    for(int j = 0;j < e[i - k].size();j++) {
        e[i].push_back(e[i - k][j]);
    }
    return cnt[i - k] + 1;
}
void init() {
    for(int i = 0;i < 16;i++) f[1 << i] = i;
    cnt[0] = 0;
    for(int i = 1;i < (1 << 16);i++) {
        cnt[i] = get_cnt(i);
    }
}
int main() {
    init();
    while(~scanf("%s",s[0])) {
        solve();
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值