题目描述
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
输入格式
输入含有多组测试数据。每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n * n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n , 当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
输出格式
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C < 2 ^ 31)。
输入输出样例
输入
2 1
#.
.#
4 4
…#
…#.
.#…
#…
-1 -1
输出
2
1
题目链接
分析:
题目要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,而且棋盘不是完整的,只有是‘#’的区域才可以放棋子。先介绍得到一个符合条件的放法的实现过程,大概的思路是在矩阵中从上到下放棋子,保证每一行至多放一个,然后用vis[j] = 1记录第j列已经放了棋子。每次放棋子的时候判断这个位置是否是棋盘区域(即a[i][j]是否是’#’),并判断这个位置所在的列数是否已经有了棋子(即vis[j]是否等于1),如果都满足的话,就把棋子放进去(记得vis[j]要更新为1)。
具体怎么实现每一次符合条件的操作呢?这就需要用到dfs(深度优先搜索)了,首先要在主函数调用dfs(0, 0),dfs(x, y)函数中的第一个参数x是指棋子已经放到了前x行,第二个参数是已经放了y个棋子。当遇到有地方恰好可以放棋子的时候,先标记vis[j] = 1;然后在dfs(x, y)函数中再调用dfs(i + 1, y + 1) 函数,代表的是现在前i列已经放了棋子(为什么是前i列,而不是是前x列?因为我们不能保证每一行都有棋盘的位置,i的范围是从x到n - 1),当在dfs(i + 1, y + 1)函数中下一个棋子放哪都不行的时候,返回到前一个dfs(x, y)函数中,执行vis[j] = 0,代表第i行第j列不能放当前的棋子,不然后面的棋子放哪都不行,需要把当前这个原本符合的棋子重新找新的地方放。这是一个递归的过程,直到所有的棋子数已经放入矩阵中,计数器s++,并返回到前一个dfs函数中,不断回溯,寻找下一个符合条件的情况。
这道题需要注意的地方:
如果是用scanf("%c", &a[i][j])输入字符,记得在每行字符输入之后加个getchar(),以消去要输入下一行字符时的回车键。在新矩阵求最多摆放的方案数目之前,要记得用memset函数把vis数组清零。
代码如下:
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
using namespace std;
typedef long long LL;
char a[15][15];
int vis[15], n, k, s;
int read(){
int x, f = 1;
char ch;
while(ch = getchar(), ch < '0' || ch > '9') if(ch == '-') f = -1;
x = ch - '0';
while(ch = getchar(), ch >= '0' && ch <= '9') x = x * 10 + ch - 48;
return x * f;
}
void dfs(int x, int y){
int i, j;
if(y == k){
s++;
return;
}
for(i = x; i < n; i++){
for(j = 0; j < n; j++){
if(!vis[j] && a[i][j] == '#'){
vis[j] = 1;
dfs(i + 1, y + 1);
vis[j] = 0;
}
}
}
return;
}
int main(){
int i, j;
while(1){
s = 0;
memset(vis, 0, sizeof(vis));
n = read();
k = read();
if(n == -1 && k == -1) break;
for(i = 0; i < n; i++){
for(j = 0; j < n; j++){
scanf("%c", &a[i][j]);
}
getchar();
}
dfs(0, 0);
printf("%d\n", s);
}
return 0;
}