练习题讲解-【搜索算法】置棋问题

来源:互联网 发布:西班牙语 知乎 编辑:程序博客网 时间:2024/06/14 02:36

【基础算法】置棋问题


题目描述:
在M*N的主格中任意指定X个格子构成一个棋盘,而其它格子是残缺的,不能放棋子。在任一个构成的棋盘上放置K个棋子,要求任意两个棋子不得位于同一行或同一列上。求满足条件的所有方案数。
注意:棋盘是稀疏的,即X<(M*N)/2
编程任务:
1. 对给定的一个棋盘,求出该棋盘可放置的最多的棋子数P;
2. 该棋盘上恰好放置i个棋子时的方案总数(1≤i≤P),其中经旋转和镜面反射而得的方案记为不同的方案

输入:
第1行:2个整数M,N,表示棋盘的行数和列数( 1<M,N<10)
接下来是M行,每行N个空格分开的数字(0或者1),1表示相应的格子在棋盘上,0表示相应的格子不在棋盘上。

输出:
第1行:1个整数表示可放置的最多的棋子数P 接下来P行,每行分别列出放i(1≤i≤P)个棋子时的方案总数。

样例输入:
5 4
0 1 1 1
0 1 0 0
1 1 1 0
0 0 1 0
0 0 1 1
样例输出:
4
1:10
2:28
3:24
4:5


题目分析:
此题与八皇后问题极其相似,而且相对简单,只需要考虑行和列(八皇后问题需要考虑对角线)。虽然题目这样描述:

要求任意两个棋子不得位于同一行或同一列上

但是我们其实只用考虑行和列中的一个(作者选用行row),其中原因作者之后会详细介绍。

首先我们需要一个二维的bool数组(chess)来模拟矩阵中的棋盘(可以放置棋子的位置),读入r,c(行,列边界)后,直接读入矩阵(虽然C++并不完全认可bool类型的输入输出,但我们仍然可以用scanf读入bool类型,非0即真)。

然后我们看到题目要求求出放置i枚棋子的方法总数和棋子的最大放置量,因此我们可以定义一个循环变量(全局)need=1来表示放置的棋子总数。但是题目要求的是输出最大放置量后再输出每一次的方法总数,我们就需要用一个一维数组answer保存答案。每一次的答案我们可以用全局变量tot储存,注意进行下一次循环时要将tot清0。在(无限)循环中,我们需要调用求方案数的函数。

现在作者可以解释为什么只需要判断行和列中的一个了——题目中描述:

棋盘是稀疏的,即X<(M*N)/2

这一点非常重要,为了避免重复超时,我们每放置一枚棋子就跳转到下一列(行)继续放置,这样就不会有同一列上有多个棋子的情况。但是有一个情况必须考虑:该列没有棋子,因此在枚举棋子的位置之前先要尝试跳转到下一列(行),把当前列留空。

之后就是写函数flag(int y,int put)。flag内部的参数有两个——当前列y,已经放置的棋子数量put。我们还需要一个边界,同样是两种情况:每一列都枚举了,但放置棋子数量不够(put < need)放置的棋子数量已经达到(put==need),方案数加1(tot++)。这两种情况都应该return。然后尝试把当前列留空(flag(y+1,put)),直到函数再次递归回来,就for循环枚举x的值。判断是否满足条件(if(!row[x] && chess[x][y])),若满足,则进行下一次放置,当递归回来时,需要回溯

当所有情况枚举完毕,回到for循环,判断tot的值,若为零(无法放置),则退出,否则将其保存在answer中(answer[need]=tot)。

最后输出need-1的值(need为无法放置时的棋子个数),再输出答案。


程序样例:

#include<cstdio>bool row[15],chess[15][15]; //行,棋盘int r,c,need,tot=0,answer[15]; //边界r,c;此时需要放置的棋子总数;答案void flag(int y,int put){    if(put==need) {tot++;return;} //放置完成    if(y>=c) return; //超出边界    flag(y+1,put); //此列留空    for(int x=0;x<r;x++) //枚举        if(!row[x] && chess[x][y])        {            row[x]=true;            flag(y+1,put+1); //下一列            row[x]=false;        }}int main(){    scanf("%d%d",&r,&c);    for(int i=0;i<r;i++) //输入        for(int j=0,x;j<c;j++)            scanf("%d",&chess[i][j]);    for(need=1;1;need++)    {        tot=0; //清0        flag(0,0);        if(tot==0) break; //无法放置        answer[need]=tot; //保存答案    }    printf("%d\n",need-1); //输出    for(int i=1;i<need;i++)        printf("%d:%d\n",i,answer[i]);    return 0;}