题目重现
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
输入描述
输入含有多组测试数据。
每组数据的第一行是两个正整数:
n,k(0<k≤n≤8)
,用一个空格隔开,表示了将在一个
n×n
的矩阵内描述棋盘,以及摆放棋子的数目。
当为-1 -1
时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 #
表示棋盘区域, .
表示空白区域(数据保证不出现多余的空白行或者空白列)。
输出描述
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证 C<231 )。
样例输入
2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1
样例输出
2
1
题解
基本的路线就是搜索了,同八皇后问题类似,利用回溯法暴力搜索即可。
本题的剪枝条件较为简单,在判定是否落子的时候满足以下情形之一的即可剪枝:
- 所在位置为
.
表示此处不能落子。 - 与已落棋子位于同一行或同一列。
- 已落棋子等于k。
当剪枝越少时意味着搜索算法代价越高,即当棋盘上没有.
全是#
的时候可以得到答案的上界:
即最终答案。
但搜索算法的代价不止于此,在每个点要判断是否要剪枝需要付出额外的代价。
数据结构 & 算法要点
- 将棋盘以0-1邻接矩阵的形式来存储可以在很小的常数时间内判断一个给定的(行、列)元组,这个位置是否可落子。(
bool board[N][N];
) - 按行搜索,可以保证棋子不在同一行。
- 保存列是否被占用的情况即可在很小的常数时间内判断一个给定的列是否已经被使用,维护这个值的正确性也只需要一个很小的常数时间。(
bool isColumnUsed[N]
) - 不需要在每个case之前初始化
board, isColumnUsed
,要保证第一个case 之前isColumnUsed
全为false
,程序可以保证isColumnUsed
在case 结束时复原。
数学漫谈
F(n,k)
关于 n 递增:
F(n,k) 关于 k 先递增后递减,其递增区间:
即 F(n,k) 关于 k 在
则 maxF(n,k)=max{F(n,⌊2n+1−4n+5√2⌋),F(n,⌈2n+1−4n+5√2⌉)}
对于 0<k≤n≤8 , maxF(n,k)=maxF(8,k)=max{F(8,5),F(8,6)}=F(8,6)=564480