简单搜索——棋盘问题

题目

在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。

输入

输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。

输出

对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。

示例

输入

2 1
#.
.#
4 4
…#
…#.
.#…
#…
-1 -1

输出

2
1

思路

该题是简单搜索的题目中应用深度优先搜索的经典题目,主要实现思想就是深度优先搜索。
深度优先搜索的相关知识可参照我之前总结的内容:简单搜索(广度优先搜索、深度优先搜索)总结
从每一处可放置棋子的位置开始进行深搜,每搜到一种可行的棋子摆放方案后计数加一,若未搜索到不进行操作;搜索到最深处后,即搜到了可行方案或者遍历完棋盘后,回溯到尚有分支未进行深搜的节点,读取已经记录下的当时状态,进行其他分支的深搜,直到所有节点被处理到,即获得全部棋子摆放方案的个数。

其中需要注意的点是:
每到一个节点,需要记录当时的状态,包括已经放置的棋子个数和之前放置棋子已经占用的列,进行后续深搜前不要破坏此状态的值,这样在深搜回溯到此节点时,仍然保留着此时的状态,便于后续进行该节点其他分支的深搜。

代码

#include 
#include 

using namespace std;

// 定义棋盘大小为 n*n,棋子数为 k
int n, k;
// 定义棋盘
char cb[20][20];
// 定义棋子摆放方案数目
int c;

/*
    x: 最近一次放置的棋子位于 x 行
    y: 最近一次放置的棋子位于 y 行
    cnt: 记录最近一次放置棋子后,已经放置了多少枚棋子
    cb_col[20]: 记录最近一次放置棋子后,各列被棋子占用的情况
*/
void dfs(int x, int y, int cnt, int cb_col[20])
{
    // 如果最近一次放置棋子后,已经放置完所有棋子,则棋子摆放方案数目加一
    if (cnt==k)
    {
        c++;
        return;
    }

    // 最近一次放置的棋子位于 (x, y)
    // 从下一行,即第 x+1 行开始,逐列进行遍历
    for (int i=x+1;i<=n;i++)
        for (int j=1;j<=n;j++)
        {
            // 当遇到可摆放棋子的位置,并且该列未放置过棋子时,将该列放置棋子,进行后续深搜
            if ((cb[i][j] == '#')&&(cb_col[j]==0))
            {
                // 定义新的列标识数组,记录此次放置棋子后,各列被棋子占用的情况
                // 用新的原因在于避免破坏当前列标识数组的记录情况
                // 当深搜回溯到此处时还能取到此时的列标识情况,即未更改的 cb_col
                int next_cb_col[20];
                // 将新的列标识数组先更新为此时的列标识情况
                for (int l=0;l<20;l++)
                {
                    next_cb_col[l]=cb_col[l];
                }
                // 由于遇到了可摆放棋子的位置,并且该列未放置过棋子
                // 所以可以将该列放置棋子
                next_cb_col[j]=1;
                // 定义新的变量,记录此次放置棋子后,已经放置了多少枚棋子
                // 用新的原因在于避免破坏当前已放置棋子个数的记录情况
                // 当深搜回溯到此处时还能取到此时的记录信息,即未更改的 cnt
                int next_cnt = cnt+1;
                // 在 (i, j) 处放置棋子后,继续深度优先搜索,摆放剩余的棋子
                dfs(i, j, next_cnt, next_cb_col);
            }
        }
}

int main()
{
    while (1)
    {
        cin>>n>>k;
        if (n==-1&&k==-1) return 0;

        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                cin>>cb[i][j];

        c=0;

        // 遍历棋盘上每一处位置
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
            {
                // 当遇到可摆放棋子的位置时,便将该位置作为棋子摆放的起始位置,即第一个棋子进行摆放
                // 并开始深度优先搜索,记录深搜过程中可行的棋子摆放方案
                // 不一定每一个起始位置都能够找到可行的棋子摆放方案,但是以每一个 '#' 都作为起始位置,是为了避免有遗漏
                if (cb[i][j] == '#')
                {
                    // 定义列标识数组,用以记录棋子摆放所占列
                    // 列为 0 表示该列尚未放置棋子
                    // 列为 1 表示该列已经放置棋子
                    int cb_col[20];
                    // 初始化所有列为 0
                    memset(cb_col, 0, sizeof(cb_col));
                    // 在 j 列放置棋子
                    cb_col[j]=1;
                    // 以 (i, j) 处放置第一枚棋子为开始,进行深度优先搜索,摆放剩余的棋子
                    dfs(i, j, 1, cb_col);
                }
            }

        cout<<c<<endl;
    }
    return 1;
}

你可能感兴趣的:(C++算法训练,c++,算法,dfs,深度优先搜索)