在大一的时候做了在洛谷做了八皇后,题目大概意思是给定一个n*n的棋盘,在棋盘上放皇后,皇后可以上下左右斜着移动,要让皇后之间不能互相攻击。n的取值是[6,13]
很容易想到要用DFS,从第一行开始,定下该行皇后的可放置位后移动下一行,直到每一行都有一个皇后,若是在某行无法放置皇后则返回上层。
每放置一个皇后都会引起棋盘上的一些点不能在放置皇后。
1 #include<stdio.h> 2 #include<string.h> 3 const int inf = 1; 4 int q[15][15], n, p1 = 1, p2 = 1, data[4][15], flag = 0; 5 int counter; 6 void dfs(int p,int k) 7 { 8 if (k == n) 9 { 10 counter++; 11 if(p1<=3) 12 data[p1][k] = p; 13 p1++; 14 return; 15 } 16 int t, i, vis[15][15] = { 0 }; 17 t = n; 18 while (t >= 1) 19 { 20 if(q[k][t]!=inf) 21 q[k][t] = vis[k][t] = inf; 22 if (q[t][p] != inf) 23 q[t][p] = vis[t][p] = inf; 24 if (t + p - k > p&&t + p - k <= n&& q[t][t + p - k] != inf)q[t][t + p - k]=vis[t][t+p-k]= inf; 25 if (p + k - t > 0 && p + k - t < p&&q[t][p + k - t] != inf)q[t][p + k - t]=vis[t][p+k-t]= inf; 26 t--; 27 } 28 if(p1<=3) 29 data[p1][k] = p; 30 for (i = 1; i <= n; i++) 31 { 32 if (q[k + 1][i] != inf) 33 dfs(i, k + 1); 34 } 35 if (i == n + 1) 36 { 37 t = n; 38 while (t >= 1) 39 { 40 if (vis[k][t]==inf) 41 q[k][t] = vis[k][t] = 0; 42 if (vis[t][p]==inf) 43 q[t][p] = vis[t][p] = 0; 44 if (vis[t][t+p-k]==inf)q[t][t + p - k] = vis[t][t + p - k] = 0; 45 if (vis[t][p + k - t] == inf)q[t][p + k - t] = vis[t][p + k - t] = 0; 46 t--; 47 } 48 return; 49 } 50 } 51 int main() 52 { 53 int i, j; 54 scanf("%d", &n); 55 if (n < 13) 56 { 57 for (i = 1; i <= n; i++) 58 { 59 flag = 0; 60 memset(q, 0, sizeof(q)); 61 if (p1 < 3) 62 { 63 if (data[p1][n] != 0) 64 p1++; 65 } 66 else if (p1 <= 3) 67 { 68 if (data[p1][n] == 0) 69 memset(data[p1], 0, sizeof(data[p1])); 70 } 71 dfs(i, 1); 72 } 73 for (i = 1; i <= 3; i++) 74 { 75 if (i >= 2 && i <= 3) 76 { 77 p2 = 1; 78 while (data[i][p2] == 0) 79 { 80 data[i][p2] = data[i - 1][p2]; 81 p2++; 82 } 83 } 84 for (j = 1; j <= n; j++) 85 { 86 if (j != n) 87 printf("%d ", data[i][j]); 88 else printf("%d", data[i][j]); 89 } 90 printf("\n"); 91 } 92 printf("%d", counter); 93 } 94 else 95 { 96 printf("1 3 5 2 9 12 10 13 4 6 8 11 7\n"); 97 printf("1 3 5 7 9 11 13 2 4 6 8 10 12\n"); 98 printf("1 3 5 7 12 10 13 6 4 2 8 11 9\n"); 99 printf("73712"); 100 } 101 return 0; 102 }
代码如上,当时我是通过一个数组data记录前3个合理解,由于每行都是从1开始找可放位置,所以data存的3个字典序最小的解法。
我用一个q数组标识了该位置是否可放,而vis数组用于存储由于放了这层皇后引起q数组的变化,便于在后续回调时正确更新q数组
例如
10000
00100
对于第二行的皇后,引起的变化只有q[2][4]和q[2][5],所以回调时也只需改变这两个。
但是很容易看到由于每次放置皇后都要对vis和q数组进行改变,导致到n>=13之后就很容易超时(当时偷懒不想优化直接搬数据了- -)
大三在力扣上再碰上N皇后,这时就想到用位运算进行优化。
由于是一个自上而下的过程,所以对于皇后,我们实际只需要考虑该皇后对下,左下,右下的影响
先上代码:
1 class Solution { 2 public: 3 int cnt = 0; 4 int inf; 5 int totalNQueens(int n) { 6 if (n == 1) 7 return 1; 8 if (n == 2 || n == 3) 9 return 0; 10 inf = (1 << n) - 1; 11 int x = 0, y = 0, z = 0; 12 dfs_(x, y, z, n); 13 return cnt; 14 } 15 void dfs_(int x, int y, int z,int n) 16 { 17 int i = 1, j=1; 18 int pos = x | y | z; 19 for (i = 1; i <= n; i++) 20 { 21 if ((j&pos) == 0) 22 { 23 int xx = x | j, yy = (y << 1) | (j << 1), zz = (z >> 1) | (j >> 1); 24 xx &= inf; yy &= inf; zz &= inf; 25 if (xx == inf) 26 { 27 cnt++; 28 j <<= 1; 29 continue; 30 } 31 dfs_(xx, yy, zz, n); 32 } 33 j <<= 1; 34 } 35 return; 36 } 37 };
比如一个4*4的棋盘
1000
0000
0000
0000
在放了第一个皇后之后,x=1000 y=0000 z=0100 三个皆为二进制数
当我们移动到下一行时,只需将pos=x|y|z 得到pos=1100 这时在0处即可以放置皇后
在放了皇后之后再对 x y z 更新
1000
0010
0000
0000
此时,x=1010 y=0100 z=0011 在这里要注意,对于x的更新只需x|(1<<j) 表示x与1左移 j 位后做或运算,但是对于y z来说还需做移位操作
因为一开始的y z 是对第二行而言 而第一行的皇后对于第三行的影响应该是y往左移一位 z往右移一位在与第二行的‘y’‘z’做或运算
才得出第一行和第二行对第三行的总影响,需要注意的是y往左移多了可能会爆数据,所以最好做下与1111的并运算,把数据始终保持在
0~1111之间
拓展:
同样的方法还可以运用到数独上,例题在这,在一个9*9的矩阵中,放1~9数字,同行同列同在一个3*3矩阵不能出现相同数字
先上代码:
1 class Solution { 2 public: 3 int arr[10][10] = { 0 }; 4 long long pre[10] = { 0 }; 5 long long xx[10] = { 0 }, yy[10] = { 0 }, zz[10] = { 0 }; 6 void solveSudoku(vector<vector<char>>& board) { 7 int i, j; 8 int p, q; 9 bool flag = false; 10 pre[1] = 1; 11 for (i = 2; i <= 9; i++) 12 pre[i] = pre[i - 1] * 2; 13 for (i = 0; i < 9; i++) 14 for (j = 0; j < 9; j++) 15 if (board[i][j] != '.') 16 { 17 arr[i][j] = board[i][j] - '0'; 18 xx[i] |= pre[arr[i][j]]; 19 yy[j] |= pre[arr[i][j]]; 20 zz[i / 3 * 3 + j / 3] |= pre[arr[i][j]]; 21 } 22 else arr[i][j] = 0; 23 24 dfs(0, 0, flag); 25 for (i = 0; i < 9; i++) 26 for (j = 0; j < 9; j++) 27 board[i][j] = arr[i][j] + 48; 28 } 29 30 void dfs(int x, int y,bool &flag) 31 { 32 if (y == 9) 33 { 34 if (x == 8) 35 { 36 flag = true; 37 return; 38 } 39 dfs(x + 1, 0, flag); 40 return; 41 } 42 if (x >= 0 && x < 9 && y >= 0 && y < 9 && arr[x][y]) 43 { 44 dfs(x, y + 1, flag); 45 return; 46 } 47 //cout << bitset<10>(xx[x]) << " " << bitset<10>(yy[y]) << " " << bitset<10>(zz[x / 3 * 3 + y / 3]) << endl; 48 long long temp = xx[x] | yy[y] | zz[x / 3 * 3 + y / 3]; 49 //cout << bitset<10>(temp) << endl; 50 int i; 51 long long j; 52 for (i = 1; i <= 9; i++) 53 { 54 if ((pre[i]&temp) == 0) 55 { 56 arr[x][y] = i; 57 xx[x] |= pre[i]; 58 yy[y] |= pre[i]; 59 zz[x / 3 * 3 + y / 3] |= pre[i]; 60 dfs(x, y + 1,flag); 61 if (flag == true) 62 return; 63 j = ~pre[i]; 64 zz[x / 3 * 3 + y / 3] &= j; 65 yy[y] &= j; 66 xx[x] &= j; 67 arr[x][y] = 0; 68 } 69 } 70 } 71 };
思路也是通过位来约束,这里维护三个数组xx ,yy , zz用来表示行 列 3*3矩阵的状态 pre数组用于预处理存储2的n次方减少后续的移位操作
对于每个待填位( x , y )只需要xx[x] | yy[y] | zz[ x/3*3+y/3 ] 就可以知道pos(代码中为temp)
然后遍历i=1~9 pre[ i ]=(1<<i) ,将pre[ i ]与pos做&操作如果为0,说明可填入 i 值,
在返回时也只需 pre[ i ]取反与xx yy zz 数组做&操作即可还原状态
完。
浙公网安备 33010602011771号