试题 基础练习 2n皇后问题
资源限制
时间限制:1.0s 内存限制:512.0MB
问题描述
给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。
输入格式
输入的第一行为一个整数n,表示棋盘的大小。
接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。
输出格式
输出一个整数,表示总共有多少种放法。
样例输入 #1 | 样例输出 #1 |
---|---|
4 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 | 2 |
样例输入 #2 | 样例输出 #2 |
---|---|
4 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 | 0 |
做这个题之前我们先来看另外一个简单点的题:
P1219 [USACO1.5]八皇后 Checker Challenge
P1219 USACO1.5 八皇后 Checker Challenge - 洛谷
题目描述
一个如下的 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 大小的。
输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
输入输出样例
输入 #1 | 输出 #1 |
---|---|
6 | 2 4 6 1 3 5 3 6 2 5 1 4 4 1 5 2 6 3 4 |
说明/提示
【数据范围】
对于 100% 的数据,6≤n≤13。
题目翻译来自NOCOW。USACO Training Section 1.5
本题的思路
经典的深搜题目
首先:主对角线行-列的差相同,副对角线行+列的和相同。
那么基本思想就是用三个数组分别记录每一列、左对角线、右对角线上是否有棋子,那么在下次放置之前便可检查某个位置能不能放棋子。再用一个数组记录该行上放置的棋子在哪一列即可。
//c++ --std=c++99
#include <cstdio>
const int MAXV = 120 + 5;
int n, ans;
int line[MAXV];
bool colum[MAXV], ldiag[MAXV * 3], rdiag[MAXV * 3];
void dfs(int);
int main(){
scanf("%d", &n);
dfs(1);
printf("%d", ans);
return 0;
}
void dfs(int num){
if (num == n + 1){ //找到一组解
ans++;
if (ans <= 3){ //输出前三个结果
for (int i = 1; i <= n; i++)
printf("%d ", line[i]);
printf("\n");
}
}
for (int l = 1; l <= n; l++){
if (!colum[l]
&& !ldiag[l - num + 50]
&& !rdiag[l + num]){ //该位置可以放置
line[num] = l; //记录该行放置棋子在哪一列
colum[l] = true,
ldiag[l - num + 50] = true,
rdiag[l + num] = true; //标记列与两个对角线
dfs(num + 1);
colum[l] = false,
ldiag[l - num + 50] = false,
rdiag[l + num] = false; //撤销标记
}
}
}
//java
import java.util.Scanner;
public class Main {
static int MAXV = 120 + 5;
static int n, ans;
static int line[];
static boolean colum[], ldiag[], rdiag[];
public static void memoryAlloc() {
line = new int[MAXV];
colum = new boolean[MAXV];
ldiag = new boolean[MAXV * 3];
rdiag = new boolean[MAXV * 3];
}
public static void dfs(int num) {
if (num == n + 1) { // 找到一组解
ans++;
if (ans <= 3) { // 输出前三个结果
for (int i = 1; i <= n; i++)
System.out.printf("%d ", line[i]);
System.out.printf("\n");
}
}
for (int l = 1; l <= n; l++) {
if (!colum[l] && !ldiag[l - num + 50] && !rdiag[l + num]) { // 该位置可以放置
line[num] = l; // 记录该行放置棋子在哪一列
colum[l] = true;
ldiag[l - num + 50] = true;
rdiag[l + num] = true; // 标记列与两个对角线
dfs(num + 1);
colum[l] = false;
ldiag[l - num + 50] = false;
rdiag[l + num] = false; // 撤销标记
}
}
}
public static void main(String[] args) {
memoryAlloc();
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
dfs(1);
System.out.println(ans);
}
}
那么我们现在回到原题。
思路
在放置每一组皇后时检查该位置是否能放置皇后,每放置一组之后标记该位置已放置。同时仿照上一题方法进行位置标记。
//c++
#include <cstdio>
const int MAXV = 8 + 2;
int n, ans;
int line_1[MAXV];
bool colum_1[MAXV],
ldiag_1[MAXV * 3],
rdiag_1[MAXV * 3];
int line_2[MAXV];
bool colum_2[MAXV],
ldiag_2[MAXV * 3],
rdiag_2[MAXV * 3];
bool map_p[MAXV][MAXV],
map_1[MAXV][MAXV];
void dfs_1(int);
void dfs_2(int);
int main(){
scanf("%d", &n);
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
scanf("%d", &map_p[i][j]);
map_p[i][j] = !map_p[i][j];
}
}
dfs_1(1);
printf("%d", ans);
return 0;
}
void dfs_1(int num_1){
if (num_1 == n + 1){ //找到第一组的一个解
dfs_2(1);
}
for (int l = 1; l <= n; l++){
if (!colum_1[l]
&& !ldiag_1[l - num_1 + 10]
&& !rdiag_1[l + num_1]
&& !map_p[num_1][l]){ //该位置可以放置
colum_1[l] = true,
ldiag_1[l - num_1 + 10] = true,
rdiag_1[l + num_1] = true; //标记列与两个对角线
map_1[num_1][l] = true; //标记棋盘第一组位置
dfs_1(num_1 + 1);
colum_1[l] = false,
ldiag_1[l - num_1 + 10] = false,
rdiag_1[l + num_1] = false;
map_1[num_1][l] = false; //撤销标记
}
}
}
void dfs_2(int num_2){
if (num_2 == n + 1){ //找到第二组的一组解
ans++;
}
for (int l = 1; l <= n; l++){
if (!colum_2[l]
&& !ldiag_2[l - num_2 + 10]
&& !rdiag_2[l + num_2]
&& !map_p[num_2][l]
&& !map_1[num_2][l]){ //该位置可以放置
colum_2[l] = true,
ldiag_2[l - num_2 + 10] = true,
rdiag_2[l + num_2] = true; //标记列与两个对角线
dfs_2(num_2 + 1);
colum_2[l] = false,
ldiag_2[l - num_2 + 10] = false,
rdiag_2[l + num_2] = false; //撤销标记
}
}
}
//java
import java.util.Scanner;
public class Main {
final static int MAXV = 8 + 2;
static int n, ans;
static int line_1[];
static boolean colum_1[], ldiag_1[], rdiag_1[];
static int line_2[];
static boolean colum_2[], ldiag_2[], rdiag_2[];
static boolean map_p[][], map_1[][];
public static void dfs_1(int num_1) {
if (num_1 == n + 1) { // 找到第一组的一个解
dfs_2(1);
}
for (int l = 1; l <= n; l++) {
if (!colum_1[l] && !ldiag_1[l - num_1 + 10] && !rdiag_1[l + num_1] && !map_p[num_1][l]) { // 该位置可以放置
colum_1[l] = true;
ldiag_1[l - num_1 + 10] = true;
rdiag_1[l + num_1] = true; // 标记列与两个对角线
map_1[num_1][l] = true; // 标记棋盘第一组位置
dfs_1(num_1 + 1);
colum_1[l] = false;
ldiag_1[l - num_1 + 10] = false;
rdiag_1[l + num_1] = false;
map_1[num_1][l] = false; // 撤销标记
}
}
}
public static void dfs_2(int num_2) {
if (num_2 == n + 1) { // 找到第二组的一组解
ans++;
}
for (int l = 1; l <= n; l++) {
if (!colum_2[l] && !ldiag_2[l - num_2 + 10] && !rdiag_2[l + num_2] && !map_p[num_2][l]
&& !map_1[num_2][l]) { // 该位置可以放置
colum_2[l] = true;
ldiag_2[l - num_2 + 10] = true;
rdiag_2[l + num_2] = true; // 标记列与两个对角线
dfs_2(num_2 + 1);
colum_2[l] = false;
ldiag_2[l - num_2 + 10] = false;
rdiag_2[l + num_2] = false; // 撤销标记
}
}
}
public static void memoryAlloc() {
map_p = new boolean[MAXV][MAXV];
map_1 = new boolean[MAXV][MAXV];
line_1 = new int[MAXV];
colum_1 = new boolean[MAXV];
ldiag_1 = new boolean[MAXV * 3];
rdiag_1 = new boolean[MAXV * 3];
line_2 = new int[MAXV];
colum_2 = new boolean[MAXV];
ldiag_2 = new boolean[MAXV * 3];
rdiag_2 = new boolean[MAXV * 3];
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
memoryAlloc();
n = sc.nextInt();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
map_p[i][j] = (sc.nextInt() != 1);
}
}
dfs_1(1);
System.out.println(ans);
}
}