题解 SP1110 【SUDOKU - Sudoku】

紫题

2019-01-15 22:08:06

Solution

蒟蒻不会DLX,只会写爆搜,居然还能过 ~~既然3x3数独能爆搜过,4x4的为什么就不行呢~~ 3x3数独深搜的时候有一个很有效的剪枝: **每次搜索时,优先选择能填的数字最少的位置填写** 4x4的数独肯定也要加上这个剪枝 但是剪得还不够,需要更完全的判定。举个栗子: ![](http://b28.photo.store.qq.com/psb?/V11AmqcZ2HP74n/XfclUNqc7a4MMYPzKjTJ3V4752pvQoeTIkicZLAlO*M!/c/dBwAAAAAAAAA&bo=EQPjABED4wABACc!) 如图,虽然每个位置都有能填的数,但是由于下面两个B的影响,导致B不可能填入第一行的任何一个空位。类似的还有别的更复杂的情况。 所以继续加入以下的剪枝: ### 1.遍历当前所有空格 #### (1)如果某个空格不能填任何数,即判定分支失败,立即回溯; #### (2)如果某个空格只能填一个数,立即填写; ### 2.考虑所有的行/列/十六宫格: #### (1)如果某个字母无法填在该行/列/十六宫格的任何空位,立即回溯; #### (2)如果某个字母只能填在该行/列/十六宫格的某个空位,立即填写. 然后再用位运算常数优化一下,就可以欢乐的AC这道题了 $Code:$ ```cpp #include<bits/stdc++.h> using namespace std; const int nan = 131070; // (1 << 17) - 2 inline int lb(int x) { return x & (-x); } inline int num(int x, int s = 0) { for(; x; x >>= 1) if(x & 1) s++; return s; } //二进制中1的个数 vector<int> raw[3][20]; vector<int> x0[3][17][17]; string str; int tim, cnt, g[260], x2[3][17], e[260], val[3][260], h[37]; void write(int x, int s) { g[x] = s; for(int i = 0; i < 3; i++) for(int j = 0; j < 16; j++) { int x0 = raw[i][val[i][x]][j]; if(e[x0] & (1 << s)) e[x0] -= (1 << s); } } bool dfs(int step) { if(step > cnt) return true; int t[260], k, minn = 0x3f3f3f3f; for(int i = 0; i < 3; i++) for(int j = 0; j < 16; j++) x2[i][j] = nan; for(int i = 0; i < 3; i++) for(int j = 0; j <= 16; j++) for(int l = 0; l <= 16; l++) x0[i][j][l].clear(); memcpy(t, e, sizeof(e)); for(int i = 0; i < 256; i++) { //x2[j][x]表示第x个行/列/十六宫格还有哪些数没填 if(g[i]) for(int j = 0; j < 3; j++) x2[j][val[j][i]] ^= (1 << g[i]); else { int numm = num(e[i]); //如果该空格不能填任何数,立即回溯 if(numm == 0) return false; //如果只能填一个数,立即填写 if(numm == 1) { write(i, h[e[i] % 37]); if(dfs(step + 1)) return true; else { memcpy(e, t, sizeof(e)); g[i] = 0; return false; } } for(int temp = e[i]; temp; temp -= lb(temp)) { int f = h[lb(temp) % 37]; //x0[i][x][j]为第x个行/列/十六宫格中,能填下数j的格子编号 for(int j = 0; j < 3; j++) x0[j][val[j][i]][f].push_back(i); } //k为当前能填数最少的空格 if(numm < minn) k = i, minn = numm; } } for(int i = 0; i < 3; i++) for(int j = 0; j < 16; j++) for(int temp = x2[i][j]; temp; temp -= lb(temp)) { int f = h[lb(temp) % 37], len = x0[i][j][f].size(), xtemp = x0[i][j][f][0]; //如果该行/列/十六宫格中某数不能填在任何一个空格中,立即回溯 if(len == 0) return false; //如果该行/列/十六宫格中某数只能填在某一个空格中,立即填写 if(len == 1) { write(xtemp, f); if(dfs(step + 1)) return true; else { memcpy(e, t, sizeof(e)); g[xtemp] = 0; return false; } } } for(int temp = e[k]; temp; temp -= lb(temp)) { //选择当前能填数最少的空格继续搜索 int f = h[lb(temp) % 37]; write(k, f); if(dfs(step + 1)) return true; memcpy(e, t, sizeof(e)); g[k] = 0; } return false; } int main() { //将每个格子编号0~255 for(int i = 0; i < 36; i++) h[(1ll << i) % 37] = i; for(int i = 0; i < 256; i++) { //val[i][j]表示行/列/十六宫格中,编号j的格子在哪一行/列/十六宫格 //状态1表示行 状态2表示列 3表示每个十六宫格 val[0][i] = i / 16, val[1][i] = i % 16, val[2][i] = i / 64 * 4 + (i % 16) / 4; //raw[i][j][x]存储每一行/列/十六宫格中所有格子的编号 raw[0][i/16].push_back(i); raw[1][i%16].push_back(i); raw[2][i/64*4+(i%16)/4].push_back(i); } cin >> tim; while(tim--) { memset(g, 0, sizeof(g)); //g[i]表示格子当前填的数(还没填为0) //e[i]存储每个格子能填的数(二进制) for(int i = 0; i < 256; i++) e[i] = nan; cnt = 0; for(int i = 0; i < 16; i++) { cin >> str; for(int j = 0; j < 16; j++) if(str[j] == '-') cnt++; else write(i * 16 + j, str[j] - 'A' + 1); } dfs(1); for(int i = 0; i < 256; i++) { printf("%c", g[i] - 1 + 'A'); if(i % 16 == 15) printf("\n"); } printf("\n"); } return 0; } ```