从八皇后到2n皇后问题
首先让我们先来看一下八皇后问题
题目描述 一个如下的 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
上面的布局可以用序列 2 4 6 1 3 5 来描述,第 i 个数字表示在第 i 行的相应位置有一个棋子,如下:
行号 1 2 3 4 5 6
列号 2 4 6 1 3 5
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 3 个解。最后一行是解的总个数。
输入格式
一行一个正整数 n,表示棋盘是 n×n 大小的。
输出格式
输出格式 前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
以下为c++实现的源代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int n,all;
bool ck[14];
int ins[14];
bool check(int i, int j)
{
for(int k = 1; k < i; k ++)
{
if(abs(j - ins[k]) == i - k)
return false;
}
return true;
}
void dfs(int i, int num)
{
if(num == n)
{
all ++;
if(all <= 3)
{
for(int k = 1; k <= n; k ++)
{
cout << ins[k] << " ";
}
cout << endl;
}
//memset(ins,0,sizeof(ins));
return;
}
for(int j = 1; j <= n; j ++)
{
if(ck[j])
{
if(check(i,j))
{
ck[j] = false;
ins[i] = j;
dfs(i + 1,num + 1);
ins[i] = 0;
ck[j] = true;
}
}
}
}
int main()
{
memset(ck,true,sizeof(ck));
memset(ins,0,sizeof(ins));
cin >> n;
//for(int i = 1; i <= n; i ++)
dfs(1,0);
cout << all;
return 0;
}
主要思想是从1到n列中每次先从第一列开始给一个数字,并开始进行搜索,在深搜过程中用ck[n]数组判断这个数字是否被用,再用check(i,j)函数判断该列中的数字即行数组成的坐标是否会与之前的列数给定的坐标是否构成对角线,最后再进行dfs(i+1,num+1);注意的是记得回溯。
#进阶2n皇后问题
题目描述
给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。
输入
输入的第一行为一个整数n,表示棋盘的大小。
接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。
输出
输出一个整数,表示总共有多少种放法。
样例输入
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
2
2n皇后问题是对八皇后问题的一个扩展,可以以同样的思想解决此类问题。
思想也是一样,先假设对黑皇后进行放置,同样的先在每一列中放置不一样的数字,即占用不一样的行数,用ck1[n]判断是否数字可用,再用check(i,j)函数判断该坐标是否会与之前坐标构成对角线。多加的一步是你还得判断放置的坐标是否为1,即是否可用。再在放置黑皇后结束过程中在进行dfs2(i,j)重复一个放置白皇后过程,最后还是得注意回溯。
c++代码如下
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
//做着题的细节就是回溯过程得仔细,你中途改变了原始值,调用函数后得回溯,复原
int n;
int ins[9][9];
int Copy[9][9];
int black[9];
int white[9];
int flag;
bool check1[9];
bool check2[9];
int all;
bool check(int i, int j,int flag)
{
for(int k = 1; k < i; k ++)
{
if(flag == 0)
{
if(i - k == abs(j - black[k]))
return false;
}else
{
if(i - k == abs(j - white[k]))
return false;
}
}
return true;
}
void dfs2(int i)
{
if(i == (n + 1))
{
all ++;
return;
}
for(int j = 1; j <= n; j ++)
{
if(check2[j])
{
if(ins[i][j] == 1)
{
if(check(i,j,1))
{
check2[j] = false;
white[i] = j;
dfs2(i + 1);
check2[j] = true;
white[i] = 0;
}
}
}
}
}
void dfs(int i)
{
if(i == (n + 1))
{
//将所有白皇后占用的位置置0
for(int j = 1; j <= n; j ++)
{
ins[j][black[j]] = 0;
}
//memset(black,0,sizeof(black));
dfs2(1);
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= n; j ++)
ins[i][j] = Copy[i][j];
}
return;
}
for(int j = 1; j <= n; j ++)
{
//判断数字是否使用过
if(check1[j])
{
//判断该位置是否可用
if(ins[i][j] == 1)
{
//判断是否是对角线
if(check(i,j,0))
{
check1[j] = false;
//记录第i行的数字
black[i] = j;
dfs(i + 1);
check1[j] = true;
black[i] = 0;
}
}
}
}
}
int main()
{
memset(check1,true,sizeof(check1));
memset(check2,true,sizeof(check2));
cin >> n;
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= n; j ++)
scanf("%d",&ins[i][j]);
}
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= n; j ++)
Copy[i][j] = ins[i][j];
}
dfs(1);
cout << all;
return 0;
}
做这题时我最后想了很久的一个点就是能否在dfs2(i,j)之前用memset函数将black[n]数组置零,然后再将白皇后的位置用black数组表示,省的再开一个数组。但可惜不行,因为深搜过程中分支是会相互影响的,你在分支过程中做的改变,必须在dfs后再回溯,不然会直接影响到最后的结果。还有一个注意的点是你在放置黑皇后后你必须得把黑皇后所占用的坐标置零,还得在最后dfs2搜索后回溯。否则也会影响到最后结果。
这也是我的第一篇博客,也是鼓励自己能坚持写下去的动力。