1321:棋盘问题:揭开dfs的神秘面纱+一些常错的点

题目大意

在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放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;
	}
}
发布了211 篇原创文章 · 获赞 14 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/csyifanZhang/article/details/105300028