1 描述
题目描述:
我们对八皇后问题进行扩展。
国际象棋中的皇后非常神勇,一个皇后可以控制横、竖、斜线等4个方向(或者说是8个方向),只要有棋子落入她的势力范围,则必死无疑,所以对方的每个棋子都要小心地躲开皇后的势力范围,选择一个合适的位置放置。如果在棋盘上有两个皇后,则新皇后控制的势力范围与第一个皇后控制的势力范围可以进行叠加,这样随着皇后数量的增加,皇后们控制的范围越来越大,直至控制了棋盘中全部的格子。
现在我们关心的是,如果在 N×N 的棋盘上放入 M 个皇后(M个皇后相互之间不能冲突)控制棋盘中的格子,则共有多少种不同的放置方法?
输入:
N (N <= 10) M (M < N)
输出:
如果将 M 个皇后放入 N×N 的棋盘中可以控制全部棋盘中的格子,则不同的放置方法的数量
2 分析
- N皇后问题其实是上课讲过的,归属的知识点是回溯法,于是我特别去复习了一下基础版本的N皇后问题
https://blog.csdn.net/qq_41680771/article/details/121170427 - 这个题在原来的基础上做了扩展,并不是n个皇后,而是m(m<n)个皇后,要求放置以后可以控制全局,那么相邻两个皇后会有什么情况呢
1). 紧挨着
2).隔着一行
3).隔着两行
- 上面的是任意两行之间相对放置的可能的位置关系,那么在列的方向上也是一样的
- 看到这里是不是懵逼了,这么多的情况,怎么入手呢
- 我刚开始写的是这样的
traceback(level + 1, num);
traceback(level + 2, num + 1);
traceback(level + 3, num + 1);
- 就是会跳过不一样的行,已达到排列出所有的组合,但是递归结束的里面实在没有写出来,总是有重复计算,最后舍弃了这个办法
- 那么我们来想一下这个问题的本质,先看行(行和列是一样的),基础版本是n行放n个,现在是n行放m个,也就是说我要在n行里面找出m行来放皇后,是不是有点像排列组合了,而基础版就是全排列
- 然后我借鉴了之前二叉决策数的思路,每一层递归都有两个选择(作出决策),一个选择是放,一个选择是不放,递归到最后一层的时候包括的情况是 都没放——都放了,那么中间间隔0行、1行、2行这些情况绝对都包含在里面,选择符合题意能够控制住棋盘的就行了
- 伪代码
/** |
* flag: false:不放; true: 放 |
* 1.traceback(false,level,num) //level:层数,大于n时结束;num:已经放置的棋子数量 |
* if(level>n) //递归结束条件 |
* if(judge()) total++; //能够控制全局,计数器+1 |
* | 2. if(flag为false) //进入下一层 |
* 1) traceback(false,level+1,num) //num不变 |
* 2) traceback(true,level+1,num+1) |
* | 3.if(flag为true) |
* i = 1:n |
* 1). place() //判断能否放置 |
* 2). if can //能放置 |
* mark() //做好标志 |
* <1> traceback(false,level+1,num) //num不变 |
* <2> traceback(true,level+1,num+1) |
* 3). if not can |
* i ++; |
* 2. traceback(true,level,num)//第一行放置 |
* 重复上面内容 |
* */ |
- 判断能否控制整个棋盘:已知我们在放入一个皇后的时候,会相应的在其所在的行、列、正斜线、反斜线上都做好标记,所以一个方案出来以后,我们要遍历每一个格子,确保都被控制,代码中注释简明洁了,就不赘述了
3 c++代码
/*
利用了二叉选择树的模型,false:这一行不放,true:这一行放
*/
#include <cstdbool>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <math.h>
#include <queue>
using namespace std;
int m = 0;
int n = 0;
int tot = 0; //方案数量
int* row = NULL; //存储被占用的行
int* column = NULL; //存储被占用的列
int* slash = NULL; //正斜线,左上到右下的
int* reslash = NULL; //反斜线,右上到左下的
/*
先看行,有皇后跳过,没有皇后看列上有没有皇后,有跳过,没有的话再看有没有哪一条斜线控制这个格子
只要有一个格子不受控制直接返回0
*/
int judge()
{ //判断是否控制了整个棋盘
int i, j;
for(i = 1; i <= n; i++) { //遍历每一行
if(row[i] == 999) { //对于没有放皇后的行
for(j = 1; j <= n; j++) {
if(column[j] == 999) { //如果列上也没有放皇后,再看有没有斜线能控制这个格
if(!(reslash[i + j] == 1 || slash[n + i - j + 1] == 1)) { //正斜线和反斜线都没有控制这个格
return 0;
}
}
}
}
}
return 1;
}
void traceback(bool flag, int level, int num)
{ // level:棋盘的第level行, num:第num个皇后
if(level > n) { //棋盘的已经没有剩余行或者已经没有皇后剩余了,递归结束条件
/*这里不能减,本来递归进入到这里的时候这一行就是不存在的行,超出棋盘外的行
减去以后会导致重复计算两次*/
// if(flag) //这一行是棋盘外的行,如果是true的话棋子应该-1
// num -= 1;
if(num != m) //没有放完m个棋子
return;
if(judge())
tot++;
return;
}
if(n - level + num < m) //如果剩下的每一行都放皇后还会有皇后剩余,直接剪枝
return;
if(!flag) { //不放的话直接进入下一层
traceback(false, level + 1, num);
traceback(true, level + 1, num + 1);
}
else {
for(register int i = 1; i <= n; i++) {
if(column[i]==999&&reslash[level+i]==999&&slash[n+level-i+1]==999)
{
row[level] = level; //第level行有皇后
column[i] = i; //第i列有皇后
reslash[level + i] = 1;//反斜线做好标记
slash[n + level - i + 1] = 1;//正斜线做好标记
traceback(false, level + 1, num);
traceback(true, level + 1, num + 1);
// 回溯
row[level] = 999;
column[i] = 999;
reslash[level + i] = 999;
slash[n + level - i + 1] = 999;
}
}
}
}
int main()
{
// freopen("file out.txt", "r", stdin);
cin >> n;
cin >> m;
column = new int[n + 1];
row = new int[n + 1];
slash = new int[2 * n + 1];
reslash = new int[2 * n + 1];
for(register int i = 0; i <= n; i++) {
column[i] = 999; //不设为0是因为0会导致一种巧合影响后面判断是否控制了全局
row[i] = 999;
}
for(register int i = 0; i < 2 * n + 1; i++) {
slash[i] = 999;
reslash[i] = 999;
}
traceback(false, 1, 0); //不放
traceback(true, 1, 1); //放
cout << tot << endl;
return 0;
}