题意:在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
Input
输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
Output
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
题解:在n*n的棋盘上方k个问题,类似n皇后问题,但是又不完全相同,因为k<=n,并不是每一行都会放棋子,所以对于每一行都有两种选择:放棋子或者不放棋子。这也正是深搜的两个分支。我们可以从第一行开始向下搜索,如果某一列可以放置棋子,那么把这一列标记,保证以后搜索不再把棋子放置在这一列上。当找到k个放棋子的位置时,递归返回,把每一列的标记清除。保证棋子位置可以再次搜索寻找下一种放k个棋子的方案。
递归结束有两种情况:一种情况是从上往下每一行棋盘都搜索完毕,程序返回。另一种情况是找到k个放棋子的位置,找到一种解,程序不再向下执行,递归返回寻找新的方案。
#include<cstdio> #include<cstring> int n,k; int ans; char mp[10][10]; bool vis[10];//标记哪一列被使用 void dfs(int x,int cnt){ if(cnt==k){ ans++; return; } if(x==n){ return; } //这一行可以放棋子 for(int i=0;i<n;i++){ //在可以放置棋子的前提下判断是否有可以放棋子的位置 if(!vis[i] && mp[x][i]=='#'){ vis[i]=true; //标记这一列已经放置棋子 dfs(x+1,cnt+1); vis[i]=false;//递归返回时清除标记,保证能再次搜索这个位置 } } //这一行不放棋子 dfs(x+1,cnt); } int main() { while(scanf("%d%d",&n,&k)!=EOF){ if(n==-1 && k==-1) break; //初始化变量 ans=0; memset(mp,0,sizeof(mp)); memset(vis,0,sizeof(vis)); for(int i=0;i<n;i++){ scanf("%s",mp[i]); } dfs(0,0);//从第0行第0个元素开始搜索 printf("%d ",ans); } return 0; }
写法二:
从当前行开始,搜索下面的每一行,在每一层递归中,都可以从当前行x的任意一行开始搜索,那么也代表着每一行可能放棋子,也可能不放棋子。(额,二层for循环的递归不知该怎么解释是好了,如果谁有更好的解释可以在下面评论)
理解二重for循环+递归
一重for循环加上递归可以搜到每一列,对于二重for循环,在递归的每一层程序中(每一层函数),可以任意选择每一行的棋盘。例如第一层递归程序,可以选择使用第1行、第2行、...第n行棋盘,第二层递归,可以选择第2、3...n行棋盘,第三层递归可以选择第3行、第4行、...、第n行棋盘。所以当每一层递归程序任意选择一行棋盘,这样就构成了搜索的所有选择。 (解法一的两个搜索分支也组成了所有的搜索选择)
void dfs(int x,int cnt){ if(cnt==k){ ans++; return; } for(int i=x;i<n;i++){ for(int j=0;j<n;j++){ if(!vis[j] && mp[i][j]=='#'){ vis[j]=true; dfs(i+1,cnt+1); vis[j]=false; } } } }