POJ 1321 — 棋盘问题 (DFS)

ps:流水账,不喜别看,然后内心咕哝骂我一句说我啰嗦,看在我写那么长的份上也别实际喷…
谢谢…
在这里插入图片描述

深搜根本没学会,这次a出来是因为之前一个月就把这个题做过。
(我那么笨,当然是看完别人的思路和别人的代码然后想了两天,几乎把别人代码背出来才把那个题a出来的啊…)

然后又重新写,错了好多次…所以说思路还是有点混乱,不能说我会了。
在这里插入图片描述

所以趁现在思路清晰赶快写一写思路!或许以后就明白怎么回事了,然后就算不明白重新看一看也就会了!

在这里插入图片描述
深度优先搜索DFS,从某个状态开始,不断地转移直到无法转移,然后退回到前一步的状态,继续转移到其他状态,如此不断重复,直至找到最终解。

首先,找那个无法转移的状态,假如是一个n * n 的矩阵,所以一共有 n 行。然后设有k枚棋子。设方案数为cnt (cnt一开始为0)。

在这里插入图片描述
在看一遍定义:
深度优先搜索DFS,从某个状态开始,不断地转移直到无法转移,然后退回到前一步的状态,继续转移到其他状态,如此不断重复,直至找到最终解。

好了,下面就找条件。

首先找清楚无法转移的这个条件:
无法转移有两种情况。

第一种无法转移的状态是棋子全放完了,即 k == 0;
棋子都放完啦,还往下找干哈???
这不仅是无法转移的状态,这时候棋子的分布也是方案的一种,毕竟棋子都放完了。

第二种是从最后一行即第n行往上找,下一次找的是n-1行,然后n - 2…直到…
找到第 1 行后 ,就把整个矩阵遍历完了,在下面就是 n - 1 = 0。没有东西啦!!!
这个时候就真是无法转移的状态!
刚刚的那个可能没棋子了还能继续找矩阵剩下的部分,这个连剩下的部分都没得。

所以可以先写出这样几行代码

	void dfs(int n, int k){
	
		if(K == 0){ cnt++; return; }//棋子为0,方案数+1,返回上一步。
		
		if(n == 0) return;//第0行什么也没有,返回上一步。
		
		......//待续
	}

在这里插入图片描述
在看一遍定义:深度优先搜索DFS,从某个状态开始,不断地转移直到无法转移,然后退回到前一步的状态,继续转移到其他状态,如此不断重复,直至找到最终解。

接下来,对于第n行,有k这个棋子即 dfs(n , k)这个状态时,有两种选择,放,与不放。

在这里插入图片描述

不放就是从dfs(n , k)这个状态 -----> dfs(n - 1 , k )这个状态。向下找了一行,但是棋子数目不变。

放的话就是dfs(n , k) ----- > dfs(n - 1 , k - 1)棋子少了一个,然后又向下找了一行。毕竟一行只能放一个棋子。

然后到下一个状态还是有这两个选择,放或者不放…递归 , 递归…直到无法转移的状态为止。
(就是刚刚上面的两种情况啊 n == 0 || k == 0)的时候。 )

对于不放的时候要报证 n > k
因为假设n < k, n = 2 , k = 3 。只有两行, 你有三个棋子,题意是一行放一个。两行全放完还剩一个,怎么放都不能使棋子完全放完。这样转移到的不放状态就没意义了,所以前提是 n > k 时,才有不放的权力。

而对于放的时候,放完,矩阵的那一列就不能再放棋子了,所以要把那一列进行标记。
具体看看代码吧。

//本来写的小n然后发现这个n随着函数的变化在变化!!!!(作者自己理解,读者请无视)

int N; //矩阵大小
bool flag[10] = {false};//首先每一列的标记都是false,表示都没有放过棋子。

void dfs(int n , int k ){

	if( k == 0 ) {cnt ++ ;	return ;}
	if( n == 0)   return;

	
	if( n > k ) dfs( n - 1 , k ); //转移到不放的状态	

	//转移到放的状态
	for(int i = 1 ; i <= N ; i++ ){ 
		//从本行找哪一列有棋盘区域。即有‘#’mp[n][i]的位置
		 if( mp[n][i] == '#' && flag[i] == false )//而且这一列flag为false没有放过棋子
		 //才能转移到放棋子的状态
		 {
		 	flag[i] = true; //这一列标记放过了。然后棋子-1,行数-1进入下一个状态。
		 	
		 	dfs( n - 1 , k - 1 );
		 	
		 	line[i] = false;//退回该状态时,把标记初始化,棋子没有放过。
		 	//这一步可能有点难想,画画图仔细思考一下!!
		 }
	}
	return ;
}

可能写的越长,你们就会觉得不好理解,不过作为作者的我是把能够想到的全写出来了,dfs这个算法让我头疼的是没有模板,遇到问题还是要自己去判断条件,自己去找能够转移的状态,去找无法转移的状态…然后每个状态在转移的过程中又会有限制条件。
所以我觉得这个算法还是挺难的…对目前的我来说。以后这方面的题要经常练!

在这里插入图片描述

对啦,还有一个注意的一点的是…数组下标为0,然后到第0行其实还有,然后就导致第n行没有(从0开始的话到n数组越界呀),写dfs就很不方便…为了让第0行没有,第n行有。
所以在构建棋盘图的时候就这样构造:

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

下标从1开始到n结束,这样就能构成一个 n * n 的矩阵了。
在这里插入图片描述
(说笑呢…看的别人…学习了一下啦)
下面是ac代码:

#include <iostream>
#include <cstring>

using namespace std;

bool flag[10];
char mp[10][10];
int n,k,cnt;
int N;

void dfs(int n, int k)
{

    if(k == 0) { cnt++; return; }
    if(n == 0) return;

    if(n > k) dfs(n-1, k);

    for(int i = 1; i <= N; ++i)
        if(mp[n][i] == '#' && flag[i] == false)
        {
            flag[i] = true;
            dfs(n-1,k-1);
            flag[i] = false;
        }

    return;
}
int main()
{

    while(cin >> n >> k )
    {
        N = n;
        if(n == -1 && k == -1) break;

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

        memset(flag,false,sizeof(flag));
        cnt = 0;
        dfs(n,k);
        cout << cnt << endl;
    }
    return 0;
}

啊…刚刚又出bug了…bug就是刚刚那个作者理解的话
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44031744/article/details/86544820
今日推荐