此部分开始搜索专题的棋盘问题,棋盘问题多数和方向数组,标记数组,回溯算法,多支深搜等有关,需要找到所有解,然后得出最优解。
解题思路:
(1)要求访问过字母的路线,并且使得字母的不同数量为最大,那么可以从出发点开始,遍历所有的路线,把每条路线的答案都保存下来,最后取最优解。
(2)在路线中,如何让路线按照自己的规则流动起来呢?这里面有两个限定条件,其一为下一步的路线不能是已经走过的位置,其二为下一步的字母不能是已经走过的字母,那么我们可以设一个Bool的标记数组,来记录已经走过的位置,设一个Bool的vis数组,标记已经走过的字母
(3)所以可行性方案应该是当此时的位置没有被访问过,并且该字母也没有访问过的话,可以走到这里,然后步数加1,既然走到这了,那么意味着,此刻的位置需要打上标记,该位置的字母也需要打上标记
(4)走完这一步,接下来往哪里走呢?可以有4个选择,上下左右,所以可以设置一个方向数组来表示下一步的偏移量,很明显,在地图中,是不能越界的,所以即使有4种选择,必须判断是否越界,没有越界的话,才能走到下一步。
(5)如果走到终点后,发现都已经不能走了,那么应该开始回头了,联系全排列问题,回溯算法也就是恢复现场的步骤,当离开这个位置返回到上一步的时候,该位置应该取消掉标记,并且步数-1
(6)每一步位置的更新都会带来步数的变化,也就是可行解的增加,如何取最优解呢?只需要在函数的开头做一个大小判断即可,取最大值。
(7)这时候就会有人有疑问,递归函数明明都是有出口的,有return的,这里怎么没有呢?首先void函数是一个无返回值的操作函数,有无return均可,这道题的出口在哪里?出口在于把左右的路线都走完,也就是for循环都执行完,找到了所有的路线,便会退出。
如下代码是按照上,下,左,右的顺序依次判断是否会越界,是否可以走。
- 第一个位置(1,1)可以走,用掉H
- 第二个位置,从第一步往上走(越界),往下走(可以),用掉A
- 第三个位置,从第二步往上走(A已经用过),往下走(可以),用掉D
- 第四个位置,从第三步往上走(D已经用过),往下走(越界),往左走(越界),往右走,用G
- 第五个位置,从第四步往上走(可以),用掉J
- 第六个位置,从第五步往上走(可以),用掉F
- 第七个位置,从第六步往上走(越界),往下走(J用过),往左走(H用过),往右走(D用过)
- 此时已经无路可走,那么F取消掉标记,步数减1,从第五个位置开始继续,因为上已经走过,往下走,下面(J用过),往左走(A用过),往右走(H用过),那么再次回退到第四个位置,依旧按照for循环规定的方向来走,但是每层递归函数遍历的i的值是不一样的。
(8)到这里,应该读懂了此递归函数到底是如何出去的。(完结撒花)
#include<bits/stdc++.h>
using namespace std;
int r,s,ans,step;//ans表示最优解 ,step表示可行解
char a[30][30];//表示字符的二维数组
bool vis[30];//表示标记已经走过的字母
bool aa[30][30];//表示标记已经走过的位置
int hang[5]={-1,1,0,0};//表示行的偏移量数组
int lie[5]={0,0,-1,1};//表示列的偏移量数组
void dfs(int x,int y)
{
if(ans<step)//如果当前的步数大于最大值
ans=step;//最大值更新
if(aa[x][y]==0&&vis[a[x][y]-64]==0)//如果该位置没有访问过并且该字母也没有访问过
{
step++;//步数+1
vis[a[x][y]-64]=1;//标记该字母已经走过
aa[x][y]=1;//标记该位置已经访问过
for(int i=0;i<=3;i++)
{
int xx=x+hang[i];//行号更新
int yy=y+lie[i];//列号更新
if(xx>r||xx<1||yy>s||yy<1)//如果位置越界
continue;//结束当前循环
dfs(xx,yy); //继续深搜
}
vis[a[x][y]-64]=0;//回溯,标记该字母取消
aa[x][y]=0;//回溯,标记该位置取消
step--;//步数-1
}
}
int main()
{
cin>>r>>s;
for(int i=1;i<=r;i++)
for(int j=1;j<=s;j++)
cin>>a[i][j];//输入字符二维数组
dfs(1,1);//表示从左上角位置开始走
cout<<ans;
return 0;
}