描述
会下国际象棋的人都很清楚:皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将8个皇后放在棋盘上(有8 * 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题。
对于某个满足要求的8皇后的摆放方法,定义一个皇后串a与之对应,即a=b1b2…b8,其中bi为相应摆法中第i行皇后所处的列数。已经知道8皇后问题一共有92组解(即92个不同的皇后串)。
给出一个数b,要求输出第b个串。串的比较是这样的:皇后串x置于皇后串y之前,当且仅当将x视为整数时比y小。
输入
第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,包括一个正整数b(1 <= b <= 92)
输出
输出有n行,每行输出对应一个输入。输出应是一个正整数,是对应于b的皇后串。
样例输入
2
1
92
样例输出
15863724
84136275
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
int result[93][9];
int used[9][9];
int cnt;
void que(int n){
int i,j;
if(n == 9){
++cnt;
return;
}
else{
for(i = 1;i <= 8; ++i){
if(used[n][i] == 0){
for(j = cnt;j <= 92; ++j)
result[j][n] = i;
for(j = 1;j <= 8; ++j)
if(used[n][j] == 0)
used[n][j] = n;
for(j = 1;j <= 8; ++j)
if(used[j][i] == 0)
used[j][i] = n;
for(j = 1;j <= 7; ++j){//直接改同列同行上斜下斜......
if((n + j) >= 1 && (n + j) <= 8 && (i + j) >= 1 && (i + j) <= 8)
if(used[n+j][i+j] == 0)
used[n+j][i+j] = n;
if((n + j) >= 1 && (n + j) <= 8 && (i - j) >= 1 && (i - j) <= 8)
if(used[n+j][i-j] == 0)
used[n+j][i-j] = n;
if((n - j) >= 1 && (n - j) <= 8 && (i + j) >= 1 && (i + j) <= 8)
if(used[n-j][i+j] == 0)
used[n-j][i+j] = n;
if((n - j) >= 1 && (n - j) <= 8 && (i - j) >= 1 && (i - j) <= 8)
if(used[n-j][i-j] == 0)
used[n-j][i-j] = n;
}
que(n + 1);
for(j = 1;j <= 8; ++j)
if(used[n][j] == n)
used[n][j] = 0;
for(j = 1;j <= 8; ++j)
if(used[j][i] == n)
used[j][i] = 0;
for(j = 1;j <= 7; ++j){
if((n + j) >= 1 && (n + j) <= 8 && (i + j) >= 1 && (i + j) <= 8)
if(used[n+j][i+j] == n)
used[n+j][i+j] = 0;
if((n + j) >= 1 && (n + j) <= 8 && (i - j) >= 1 && (i - j) <= 8)
if(used[n+j][i-j] == n)
used[n+j][i-j] = 0;
if((n - j) >= 1 && (n - j) <= 8 && (i + j) >= 1 && (i + j) <= 8)
if(used[n-j][i+j] == n)
used[n-j][i+j] = 0;
if((n - j) >= 1 && (n - j) <= 8 && (i - j) >= 1 && (i - j) <= 8)
if(used[n-j][i-j] == n)
used[n-j][i-j] = 0;
}
}
}
}
}
int main(){
int i,j,N;
int pos[92];
scanf("%d",&N);
for(i = 0;i < N; ++i){
scanf("%d",pos + i);
}
memset(used,0,sizeof(used));
cnt = 1;
que(1);
for(i = 0;i < N; ++i){
for(j = 1;j <= 8; ++j)
printf("%d",result[pos[i]][j]);
printf("\n");
}
return 0;
}
思路一:使用二维数组保存每个位置是否可以使用。
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int shu[9],sx[17],xx[17];
int result[93][9];
int cnt;
void que(int n){
int i,j;
if(n == 9){
cnt++;
return;
}
else{
for(i = 1;i <= 8; ++i){
if((!shu[i]) && (!sx[n + i]) && (!xx[n - i + 8])){
for(j = cnt;j <= 92; ++j){
result[j][n] = i;
}
shu[i] ^= 1;
sx[n + i] ^= 1;
xx[n - i + 8] ^= 1;
que(n + 1);
shu[i] ^= 1;
sx[n + i] ^= 1;
xx[n - i + 8] ^= 1;
}
}
}
}
int main(){
int N,i,j;
int pos[93];
scanf("%d",&N);
for(i = 0;i < N; ++i)
scanf("%d",pos + i);
cnt = 1;
memset(shu,0,sizeof(shu));
memset(sx,0,sizeof(sx));
memset(xx,0,sizeof(xx));
que(1);
for(i = 0;i < N; ++i){
for(j = 1;j <= 8; ++j){
printf("%d",result[pos[i]][j]);
}
printf("\n");
}
return 0;
}
思路二:使用三个数组保存各列号是否被使用。
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int shu[9],result[93][9];
int cnt;
void que(int n){
int i,j;
if(n == 9){
for(i = 1;i <= 8; ++i)
result[cnt][i] = shu[i];
++cnt;
}
else{
for(i = 1;i <= 8; ++i){
for(j = 1;j < n; ++j){
if(shu[j] == i || abs(n - j) == abs(shu[j] - i))
break;
}
if(j == n){
shu[n] = i;
que(n + 1);
}
}
}
}
int main(){
int N,i,j;
int pos[93];
scanf("%d",&N);
for(i = 0;i < N; ++i)
scanf("%d",pos + i);
cnt = 1;
que(1);
for(i = 0;i < N; ++i){
for(j = 1;j <= 8; ++j)
printf("%d",result[pos[i]][j]);
printf("\n");
}
return 0;
}
思路三:直接使用一个数组(下标为行号)保存之前各行的列数。
八后规模还是太小,三种方法运行时间区别不大。
关于八后问题的一点感悟:
ACM不单是刷题呐,紫书上说,八后问题本质上就是一个全排列问题(列号的全排列 8!种情况),而且还没有重复元素,优化的地方就是回溯的条件。回溯一定要在所有递归函数出口处恢复被修改的值。