题目大意
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
思路分析
好久不做搜索,竟然被这样一道题卡住了,我枯了。想了好大一会dp,最后还是用了dfs,既然要用dfs,那我们对每个可选的地方就有两种做法:放和不放,如果放那么这个位置的行列和已经放了的应该不重复,如果不放就继续进行,具体到代码里我们用的是这样一种思路:
- 首先写出dfs函数声明,dfs函数有两个参数
l,sum
,l表示当前递归到哪一行,sum表示已经放了几个棋子。 - 决定搜索的终止条件:(i)如果放好了k个棋子,结果数目+1,返回。(ii)迭代进行到了n+1行,超出范围,返回。
- 如何判断某个元素是否可选:(i)我们从上到下递归,保证行之间不重复。(ii)使用一个数组
vis
记录某一次迭代对列的访问情况,如果某个位置的字符是#
而且该列没访问过,那就可以放。调用函数dfs(l+1,sum+1)
- 当我们遍历完本行,将本行的所有位置都递归完成之后,我们的任务还没有结束,此时我们只是考虑了选取这些位置,而没有考虑不选的情况,因此在循环结束之后,我们需要调用
dfs(l+1,sum)
,忽略本行的位置直接跳。
tips
有些新手比如我可能会将dfs写成这样的形式:
//l:记录当前到了第几行 sum:记录当前已经放置了几个棋子
void dfs(int l, int sum) {
if (sum == k) { res++; return; }
if (l == n + 1)return;
for (int i = 1; i <= n; i++) {
if (s[l][i] == '#' && !vis[i]) {
//vis[i]:这一次迭代中第i列是放过的
vis[i] = 1;
dfs(l + 1, sum + 1);
vis[i] = 0;//回溯
}
else
dfs(l + 1, sum);
}
}
这样是错的!将不选本行元素的情况重复了n次!
AC代码:
#include<iostream>
#include<string.h>
using namespace std;
#define MAX 10
#define ll long long
char s[MAX][MAX];
ll n, k, res = 0, vis[MAX];
//l:记录当前到了第几行 sum:记录当前已经放置了几个棋子
void dfs(int l, int sum) {
if (sum == k) { res++; return; }
if (l == n + 1)return;
for (int i = 1; i <= n; i++) {
if (s[l][i] == '#' && !vis[i]) {
//vis[i]:这一次迭代中第i列是放过的
vis[i] = 1;
dfs(l + 1, sum + 1);
vis[i] = 0;//回溯
}
}
dfs(l + 1, sum);
}
int main() {
while (cin >> n >> k && n + k != -2) {
memset(vis, 0, sizeof(vis));
res = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> s[i][j];
}
}
dfs(1, 0);
cout << res << endl;
}
}