【基础算法】置棋问题
题目描述:
在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;
}