#Virtual Judge之kuangbin带你飞题集#专题一 简单搜索 部分题解

A-棋盘问题-POJ-1321

在这里插入图片描述

Sample Input
2 1
#.
.#
4 4
…#
…#.
.#…
#…
-1 -1

Sample Output
2
1

题意

  • 简述概括为在n*n的棋盘上有标注“#”的位置可放棋子
  • 输出在棋子均不同行不同列的情况下,有多少种方法摆放k个棋子
  • 详见上述题面,已为中文版

题解

  • 此题为搜索类题目,现选用DFS
  • 递归出口应当是已按题意要求放完k个棋子,或遍历完棋盘仍无法成功放完k个棋子
  • 对于不同行不同列的要求,此题解采用按行递归遍历各列
  • 递归传入参数代表行数,用use数组标记被遍历的各列是否已被用过
  • 在每一行中遍历n列,寻找在这一行有无合适的位置可以放置一颗棋子
  • 若放置,则标记对应列的use数组,并累计放置的棋子个数,然后继续递归下一行
  • 这一行一颗都不放也是可以的,循环结束后直接递归下一行
  • 务必注意!x行选择在i列放置后,换成在其他列放置棋子,可能也满足题意
  • 因此,递归结束后,一定要记得还原这一列未放置棋子的状态,并遍历下一列
  • 对应列use数组和统计已放棋子个数的cnt还原
  • 最后递归结束时,如果是成功放置而return的,那么就在初始化为0的ans上自加,统计放置方法数

涉及知识点

  • DFS和BFS 搜索算法(此题解用的是DFS)
  • 对于DFS和BFS的搜索算法-详见链接博客介绍搜索入门

AC代码

#include<stdio.h>
#include<utility>
#include<string.h> 
using namespace std;
const int maxn=10;
int n,k,cnt,ans;
char put[maxn][maxn];
int use[maxn];//标记某列是否已被用过 
void dfs(int x)//递归行,每行最多用一次 
{
	if(cnt==k) {++ans;return;}//已用k列,代表已成功防止k个棋子 
	if(x>n) return;
	for(int i=1;i<=n;++i)//枚举列,每列最多用一次 
	{
		if(!use[i]&&put[x][i]!='.')//这一列还没用过,且不是空白区域 
		{
			use[i]=1,++cnt;//在第x行用第i列 
			dfs(x+1);
			use[i]=0,--cnt;//还原用第i列前的状态 
		}
		//继续循环可选择在第x行用第i+1列等,第i列可能以后再用 
	}
	dfs(x+1);//不用这行,直接下一行 
}
int main()
{
	while(~scanf("%d %d",&n,&k),n!=-1&&k!=-1)
	{
		memset(use,0,sizeof(use));
		ans=0,cnt=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%s",put[i]+1);
		}
		dfs(1);
		printf("%d\n",ans);
	}
	return 0;
}

B-Dungeon Master-POJ-2251

在这里插入图片描述

由于【引用】中难以输入一排连续的#,故下述输入输出的样例用代码块粘贴

*Sample Input*
3 4 5
S....
.###.
.##..
###.#

#####
#####
##.##
##...

#####
#####
#.###
####E

1 3 3
S##
#E#
###

0 0 0

*Sample Output*
Escaped in 11 minute(s).
Trapped!

题意

  • 第一行输入L/R/C,分别代表空间高度、每层的行数、每层的列数
  • 在这样一个空间中,要求从S出发,到达E
  • 主角可向上下左右前后六个方向行走,每走一次耗费1min
  • 求问从S走到E的最短时间或是否无法走到
  • 分别输出Escaped in 最短耗时 minute(s).Trapped!

题解

  • 这是一个三维的搜索题,与二维相比只要把结构体元素、put和move均改为三维即可
  • 此题解思路使用的是BFS,队列为空已到终点时退出循环,分别返回**-1最短耗时**
  • 三维的time数组记录走到每个点的最短耗时,那么若能走到终点,返回终点对应的time即可
  • 关键思路在于!初始化为0的time还可代表某个点是否已被走到过,if中需要判断走过的点不能再走
  • 因为需要输出的是最短耗时,早走到点A和晚走到点A对于之后的情况起点相同,但对于走点A肯定早走到耗时少
  • 所以就没有必要再记录绕路后的耗时
  • 未越界+走得通+未走到过时间,应当更新对应点耗时+判断是否已到终点+新起点入队
  • 最后判断返回值,若为 -1 则输出 Trapped!否则输出返回的最短耗时

涉及知识点

  • DFS和BFS 搜索算法(此题解用的是BFS)
  • 对于DFS和BFS的搜索算法-详见链接博客介绍搜索入门

AC代码

#include<stdio.h>
#include<string.h>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=50; 
char put[maxn][maxn][maxn];
int L,R,C,sum,ans,time[maxn][maxn][maxn];
int move[6][3]={{-1,0,0},{1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}};
struct node{int x,y,z;}start,ed,cur,next;//起点,终点,当前,走向的目标位置 
int bfs()
{
    queue<node> Q;//为了节省循环pop清空队列Q的时间,索性多组输入时每次重建Q即可 
    Q.push(start);
    while(Q.size())
    {
        cur=Q.front(),Q.pop();
        for(int i=0;i<6;i++)//遍历左右前后上下六个方位 
        {
			next.x=cur.x+move[i][0];
    		next.y=cur.y+move[i][1];
			next.z=cur.z+move[i][2];
			if(next.x>=0&&next.x<R&&next.y>=0&&next.y<C&&next.z>=0&&next.z<L&&put[next.z][next.x][next.y]!='#'&&time[next.z][next.x][next.y]==0)
            {//行、列、高均未越界 且 往当前遍历到的方向走得通 且 还未走到过这个目标位置(不然来回走就不是最短时间了) 
				time[next.z][next.x][next.y]=time[cur.z][cur.x][cur.y]+1;//更新走到目标位置耗费的时间 
				if(next.x==ed.x&&next.y==ed.y&&next.z==ed.z) return time[ed.z][ed.x][ed.y];//走到了终点 
                Q.push(next);//目标位置入队,接下来从目标位置开始走 
			}
		}
    }
    return -1;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	while(cin>>L>>R>>C&&L+R+C)
	{
		sum=0;
		memset(time,0,sizeof(time));
		for(int i=0;i<L;++i)
		{
			for(int j=0;j<R;++j)
			{
				for(int k=0;k<C;++k)
				{
					cin>>put[i][j][k];
					if(put[i][j][k]=='S') start.z=i,start.x=j,start.y=k;
					else if(put[i][j][k]=='E') ed.z=i,ed.x=j,ed.y=k;
				}
			}
		}
		ans=bfs();
		if(ans==-1) cout<<"Trapped!"<<endl;
		else cout<<"Escaped in "<<ans<<" minute(s)."<<endl;
	}
	return 0;
}

C-Catch That Cow-POJ-3278

在这里插入图片描述

Sample Input
5 17

Sample Output
4

在这里插入图片描述

题意

  • 第一行输入N/K,代表农夫初始位置和牛所在的位置,他们都在一条直线的路径上
  • 农夫每次可以后退一步、前进一步或者直接移动到坐标两倍处,三种移动方式每次耗时均是1min
  • 输出农夫从N移动到K的最短耗时

题解

  • 这是一个一维的搜索题,移动方式三种
  • ok函数只需判断是否越界,耗时最短可由sign标记是否已到过来保证
  • while循环中分别判断三种移动方式,且队列先进先出
  • 因此,每一次三种情况if合理的入队后,会在各自下一步之前先被取出
  • 最后,当Move取出的当前位置坐标已到K时,return退出函数,ans保存了当前位置对应的step即最短耗时,输出即可

涉及知识点

  • DFS和BFS 搜索算法(此题解用的是BFS)
  • 对于DFS和BFS的搜索算法-详见链接博客介绍搜索入门

AC代码

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<queue>
using namespace std;
const int maxn=1e5+10;
int pos,N,K,ans,sign[maxn];
struct movement
{
	int pos;
	int step;
}Move,t;
bool ok(int x)
{
	if(x<0||x>maxn) return false;
	return true;
}
void bfs()
{
	queue<movement> q;
	memset(sign,0,sizeof(sign));
	Move.pos=N;//从N开始走 
	Move.step=0;//初始化时间为0 
	q.push(Move);
	sign[N]=1;//标记是否走到过这个位置,重复走就不是最快的了 
	while(!q.empty())
	{
		Move=q.front();
		/*
			队列是先进先出的,所以每一次三种情况if合理的入队后
			会在各自下一步之前先被取出来
		*/
		q.pop();
		if(Move.pos==K)
		{
			ans=Move.step;
			return;
		}
		else t.step=Move.step+1;//用t记录走到下一个位置的耗时和位置
		t.pos=Move.pos+1;
		if(ok(t.pos)&&sign[t.pos]==0)
		{
			sign[t.pos]=1;
			q.push(t);
			//t入队后再用front提取move,得到的步数和位置都是更新过了的 
		}
		t.pos=Move.pos-1;
		if(ok(t.pos)&&sign[t.pos]==0)
		{
			sign[t.pos]=1;
			q.push(t);
		}
		t.pos=Move.pos*2;
		if(ok(t.pos)&&sign[t.pos]==0)
		{
			sign[t.pos]=1;
			q.push(t);
		}
	}
}
int main()
{
	scanf("%d %d",&N,&K);
	ans=0x3F3F3F3F;
	bfs();
	printf("%d\n",ans);
	return 0;
}

D-Fliptile-POJ-3279

在这里插入图片描述

Sample Input
4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1

Sample Output
0 0 0 0
1 0 0 1
1 0 0 1
0 0 0 0

题意

  • 有一个m行n列的01矩阵(下附代码因个人习惯,看做n行m列)
  • 每次操作选择一个格子,使得该格子与上下左右四个格子的值翻转(翻转即0变为1或1变为0)
  • 输出至少多少次操作可以使得矩阵中所有的值变为0
  • 如果不可能成功操作,则输出IMPOSSIBLE

题解

  • 这道题借鉴搜索思维的不是很多,主要就是翻转上下左右和自己五个move操作
  • 这类题目属于 翻转类开关问题,首先需要明白,翻转的顺序不影响最终结果
  • 其次要知道翻转2*k次=没翻转,因此可以用对2取余的操作来简化翻转次数,且 最后输出的矩阵只有0或1
  • 第一行是否翻转用m位的二进制数来表示(十进制里面就是 [0,2m,第i位代表第i列,1翻转0不翻转
  • 同时,对第一行 [0,2m 的遍历方式也 遵循了字典序
  • 第二行开始,根据截至当前位置,被遍历位置的上方是否需要翻转,来决定这个位置是否需要翻转
  • 所谓截至当前位置,那么就要把前面所有翻转操作对上方位置的影响进行计算
  • 初始化表示翻转方案数组全部为0,那么没遍历到的肯定算作没翻转
  • 用一个函数来计算某位置被上下左右和自己是否翻转后所得的数,然后在决定下面位置是否翻转的函数中调用即可
  • 因为每次都要保证前一行都被翻转至0,所以最后一行翻转完毕后,只要 判断最后一行是否全是0,就能知晓是否有可行方案
  • 决定是否翻转函数中顺便统计翻转次数并输出
  • 最后比较得到最小的翻转次数,每次比较出更小的次数时,就把当前翻转方案复制到最优解数组中,可用memcpy整体复制
  • 这些操作主要需要用到三个数组存图+当前翻转方案+最优解方案
  • 具体详见代码行批注

涉及知识点

  • DFS和BFS 搜索算法(此题解用的是BFS)
  • 位运算 思维的处理方式(寒假集训笔记中未涉及,详细内容后续更新)
  • 对于DFS和BFS的搜索算法-详见链接博客介绍搜索入门

AC代码

#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f; 
int a[20][20],b[20][20],c[20][20];//a存图;b保存当前翻转方案;c保存最优解
int move[5][2]={{-1,0},{1,0},{0,0},{0,-1},{0,1}};//五个翻转走向 
int n,m,ans=inf;//翻转类开关问题:翻转的顺序并不影响最终结果 
bool getcolor(int x,int y)//翻动x,y周围的瓷砖,得到x,y的颜色
{
	int res=a[x][y];
	for (int i=0;i<5;++i)
	{
		int fx=x+move[i][0],fy=y+move[i][1];
		if(fx>=1&&fx<=n&&fy>=1&&fy<=m) res+=b[fx][fy];//b[fx][fy]是1就要翻它 
	}//实际只考虑这个位置之前之上和自己翻转的影响,其他还未遍历到改变,且初始化为0 
	return res%2;//原0则翻奇次变1;原1则翻奇次变0 
}
int solve()
{
	int res=0;//从第二行开始检查是否需要翻转,上一行是1就要靠其下对应的这一行位置来翻 
	for(int i=2;i<=n;++i) {for(int j=1;j<=m;++j) {if(getcolor(i-1,j)) b[i][j]=1;}}
	for(int i=1;i<=m;++i) {if(getcolor(n,i)) return inf;}//检查最后一行是否全为0
	for(int i=1;i<=n;++i) {for(int j=1;j<=m;j++) res+=b[i][j];}//统计翻转次数
	return res;
}
int main()
{
	scanf("%d %d",&n,&m);//n行m列 
	for(int i=1;i<=n;++i) {for(int j=1;j<=m;++j) scanf("%d",&a[i][j]);}
	for(int s=0;s<1<<m;++s)//按照字典序枚举第一行所有翻转可能,1翻0不翻 
	{
		memset(b,0,sizeof(b));//初始化都不翻转 
		for(int i=1;i<=m;++i) b[1][i]=(s>>(m-i))&1;//取出s在二进制表示下的第m-i位,二进制第几位是从右数的
		int t=solve();//当前情况翻转次数 
		if(t<ans) {ans=t;memcpy(c,b,sizeof(b));}//此处的memcpy复制b中sizeof(b)个字节到c
	}
	if(ans==inf) printf("IMPOSSIBLE\n");
	else 
	{
		for(int i=1;i<=n;++i) {for(int j=1;j<=m;++j) printf("%d%c",c[i][j],j==m?'\n':' ');}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值