题目阅读
1.“摆放棋子”->与走迷宫不同的是棋子摆放出的路径并不是连续的,也就是说该步的位置和下一步可能包含上下左右相邻,右上、右下、左上、左下相邻,甚至会出现隔了几行、几列才能放置棋子的情况(根本就不相邻,这是和迷宫题最突出的不同)。也就是解向量中出现部分向量值为空的情况。
2.“任意的两个棋子不能放在棋盘中的同一行或者同一列”->无疑这是排除了上面情况的上下左右相邻情况和隔了几行几列之后仍在同行或同列的情况(是的,我们还要把隔了几行几列之后并不在同行同列的情况考虑为合法情况)。
综上所述,合法情况包括右上、右下、左上、左下相邻和隔了几行几列之后仍在同行或同列的情况。
举例来说就是:
1.
2 2
#.
.#
2.
4 3
…#
…#.
…
#…
对此,我们能够想到的对策就是行列标记,也就是建立标记某列或某行已经被访问过的行列标记数组来去除“存在两个棋子放在棋盘中的同一行或者同一列”这一非法情况。类似《计算机组成原理》译码驱动方式重合法。
3.棋子没有区别,求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
毫无疑问,需要寻找没有重复的棋子摆放方案。就像走迷宫所有的情况搜寻所有可以走的位置,然后尝试摆放,如果能摆放再去考虑下一步。如果迷宫走完或不可走,再做相应处理。(也就是减治、试探、回溯处理)
是的,我们这次又要搜索,至于选择深搜还是广搜,我们可以通过这篇博客了解下如何更好的选择:https://blog.csdn.net/qian_youyou/article/details/79230064。
简言之,最短路BFS,所有解DFS。本题考察“所有可行的摆放方案”,所以选择深搜。接下来就分析下深搜情况。
由于通常深搜方向是从上到下、从左到右,因此可以通过在某层找到可放棋子,再去寻找其它层可放位置。所以可以不考虑行标记,只保留列标记(因为从上到下优先于从左到右,如果你的深搜优先级相反,那么标记就与这里相反)。
Input
输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
这里要注意接受多组数据用while(cin>>n>>k),又因为结束为n或k=-1,那么就是
while(cin>>n>>k&&n!=-1&&k!=-1)
又因为要求解“所有可行的摆放方案C”,那么每次都要初始化摆放方案记录值、列标记。
sum=0;
memset(a,0,sizeof(a));
(注意memset需要声明#include<cstring>
)
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
同上,需要接受每组数据时接受不同的图记录。又因为图的结点元素都是字符而且是n*n的矩阵,故记录图的存储结构是二维数组。注意分配足够列空间给结束符‘\n’。
char map[10][10];
for(i=0;i<n;i++)
cin>>map[i];
Output
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
cout<<sum<<endl;
注意为输出一行需要endl换行
由于C<231,则可以考虑用int作为记录方案数的数据类型
int的取值范围为: -231——231-1
搜索部分
由题目分析可知需要考虑的递归基包括用完棋子、超出范围、已经访问过的列、图中‘.’不可放置、某层没有放置位置。
一般情况
对于访问位置,我们需要行列变量x,y标记,而我们需要枚举尝试一层的所有列,可以通过
for(j=0;j<n;j++)
实现。而枚举所有行,则可以通过dfs(x+1)表示,也就是说dfs递归需要参数x表示行。
对于结点可能存在“已经访问过的列、图中‘.’不可放置”两种情况,需要排除这两种才能安置棋子、继续向下层搜索。(减治)
if(!a[j]&&map[x][j]=='#'){
dfs(x+1);
}
安置后,为了防止之后棋子安在同列的情况,需要标记a[j]。又因为dfs需要回溯尝试所有情况,所以又要在dfs下一层后取消标记。
for(j=0;j<n;j++)
{
if(!a[j]&&map[x][j]=='#'){
a[j]=1;
dfs(x+1);
a[j]=0;
}
}
退化情况
由于出现隔层可以放置棋子的情况,所以我们x与n、k无关,需要标记放置的棋子数,这里用可放置棋子数随着放置逐渐减少来表示棋子数变化。则dfs需要参数表示可放置棋子数
dfs(int x,int step)
由于尝试该层后没有找到放置位置,那就需要调用下一层搜索
for(j=0;j<n;j++)
{
if(!a[j]&&map[x][j]=='#'){
a[j]=1;
dfs(x+1,step-1);
a[j]=0;
}
}
dfs(x+1,step);
是的,我们还要继续考虑剩下的递归基“用完棋子、超出范围”。
用完棋子无疑对应一种放置情况,所以要做sum++操作。
if(step==0){
sum++;
return;
}
而“超出范围”只可能是行超出范围,因为列控制由j负责。
if(x>=n)
{
return;
}
两者顺序是先判断是否放置完棋子,再判断是否超过范围。因为存在放置完棋子后,x=n的情况。
如:
4 4
...#
..#.
.#..
#...
最后递归入口应从第一行(起始坐标为(0,0))开始,初试step为k。
dfs(0,k);
综上,代码如下:
#include<cstring>
#include<iostream>
using namespace std;
int a[10],n,k,first;
int sum;
char map[10][10];
void dfs(int x,int step){
int j;
if(step==0){
sum++;
return;
}
if(x>=n)
{
return;
}
for(j=0;j<n;j++)
{
if(!a[j]&&map[x][j]=='#'){
a[j]=1;
dfs(x+1,step-1);
a[j]=0;
}
}
dfs(x+1,step);
}
int main(){
int i,j;
while(cin>>n>>k&&n!=-1&&k!=-1)
{ memset(a,0,sizeof(a));
sum=0;
for(i=0;i<n;i++)
cin>>map[i];
dfs(0,k);
cout<<sum<<endl;
}
return 0;
}