kuangbin简单搜索专题A~F(上)
----------------- POJ 1321, 2251, 3278, 3279, 1426, 3126
在大一上的acm讲座上接触过一些简单搜索,不过太菜还有不努力进不了队~~~
最近突然来了热情,想捡起了很久之前撇在一边的简单搜索,正巧我在scau ACM队的大佬推了一个专题给我。不得不说,这个专题让我自闭了很久(cai)。虽然参考了csdn许多大神的思想,并且我的代码又长又臭,但是做完接近一半之后还是挺兴奋的。决定跟随我的大佬,写写自己做题的感受心得,这是我的“hello world”的博客哦~~(做好被无情嘲讽的准备,欢迎来喷,嘻)
kuangbin简单搜索专题链接:https://vjudge.net/contest/65959
首先,先来复制一波:
dfs:
大体框架:
void dfs(int deep)
{
if(符合某种条件)
操作;
return;
if(deep>搜索深度)
return;
for(i=0; i<搜索深度; i++)
{
if(符合条件)
{
vis[i] = 1;
dfs(deep+1);
vis[i] = 0;
}
}
}
在递归终点一定要有return;(即便是void类型)
bfs:
实现方式:一般用队列实现(超级狭义之理解,但是POj1426 Find The Multiple 的想法让我折服!)
-
POj 1321 棋盘问题
Description
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
Input
输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。
Output
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
Sample Input
2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1
Sample Output
2
1
很显然这题是dfs。但是在做题之前,我对dfs是只知道概念而具体的实现方法脑子里一片空白。有两个实现难题摆在前面:
1、用什么方法实现纵向搜索 2、怎么保证每一层次(每一颗棋子)的横向搜索
思考许久,决定用递归以及循环的方式去实现:
由于深搜决定了两颗棋子不会在同一层,只用一个 line[ ] 数组记录棋子已经出现在的列。然后以第一颗棋子去用循环去遍历整个棋盘(通过条件排除行不通的落点),每一次找到可行落点就搜索下一颗棋子。其他棋子操作亦然。
#include<cstdio>
#include<iostream>
#include<stack>
#include<cstring>
using namespace std;
int vis[8][8]; //记录棋盘,-1为棋盘区域,0为空白区域
int line[9]; //记录已搜索棋子占据的列 ,0为已占据,-1未占据
int depth = 0; //已完成搜索的层数(确定了n颗棋子,depth==n,而不是正在搜索的层数)
int n, k, t = 0;
struct p //记录每颗棋子的坐标
{
int i, j;
};
p temp;
stack <p> dfs ; //记录搜索情况
void reader( ) //读入Input
{
char c;
memset(line, -1, sizeof(line));
memset(vis, -1, sizeof(vis));
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j)
{
if((c = getchar()) != '\n' && c == '.')
vis[i][j] = 0;
else if( c == '\n')
{
c = getchar();
if(c == '.')
vis[i][j] = 0;
}
}
}
void dfs_()
{
int i, j;
for(i = 0; i < n; ++i) //遍历整个棋局
{
if(!dfs.empty() && dfs.top().i >= i) continue; //搜索的下一颗棋子的行数必须大于上一颗
if(n - i < k - depth) return;
for(j = 0; j < n; ++j)
{
if(!line[j] || !vis[i][j]) //排除空白区以及之前棋子已在列的落点
continue;
else
{
temp.i = i; temp.j = j;
dfs.push(temp); //找到可行落点,放入栈中
line[j] = 0; //列已占据
++depth; //已完成搜索层数+1
if(depth == k) //确定全部棋子,方案+1,即将返回上一层
{
++t;
dfs.pop();
line[j] = -1;
--depth;
}
else
{
dfs_(); //depth!= k 继续向下搜
line[j] = -1;
dfs.pop();
--depth;
}
}
}
}
return;
}
int main()
{
while(scanf("%d%d",&n, &k)== 2 && n != -1 && k != -1)
{
int i, j;
reader();
dfs_();
printf("%d\n", t);
t = 0; depth = 0;
memset(vis, -1, sizeof(vis));
memset(line, -1, sizeof(line));
}
return 0;
}
冗长的代码 ~
一点点反思:不应该用全局变量去记录已完成的搜索的深度,而且函数不想用参数这个习惯我也是醉了~,这两点限制了dfs函数的灵活性,让我不得不去通过一些复杂的条件在遍历全局的时候排除不可行的情况。附上“正宗”的做法:
- POj 2251 Dungeon Master
Description
You are trapped in a 3D dungeon and need to find the quickest way out! The dungeon is composed of unit cubes which may or may not be filled with rock. It takes one minute to move one unit north, south, east, west, up or down. You cannot move diagonally and the maze is surrounded by solid rock on all sides.
Is an escape possible? If yes, how long will it take?
Input
The input consists of a number of dungeons. Each dungeon description starts with a line containing three integers L, R and C (all limited to 30 in size).
L is the number of levels making up the dungeon.
R and C are the number of rows and columns making up the plan of each level.
Then there will follow L blocks of R lines each containing C characters. Each character describes one cell of the dungeon. A cell full of rock is indicated by a '#' and empty cells are represented by a '.'. Your starting position is indicated by 'S' and the exit by the letter 'E'. There's a single blank line after each level. Input is terminated by three zeroes for L, R and C.
Output
Each maze generates one line of output. If it is possible to reach the exit, print a line of the form
Escaped in x minute(s).
where x is replaced by the shortest time it takes to escape.
If it is not possible to escape, print the line
Trapped!
Sample Input
3 4 5
S....
.###.
.##..
###.#
#####
#####
##.##
##...
#####
#####
#.###
####E
1 3 3
S##
#E#
###
0 0 0
Sample Output
Escaped in 11 minute(s).
Trapped!
bfs,典型的二维走迷宫寻找最短路径的问题的升级版!之前对于走迷宫的题我的记忆还是比较深刻的,包括scut oj网上面的马踏棋盘也是这种类型。实现方式就是:
起点作为当前状态进行搜索,将下一步(层)的所有可行状态(包括坐标时间)用队列储存,并且用一个数组在对应的坐标位置记录其时间,不断从队列头取出状态重复操作。由于先放到队列中的状态对应的时间(已经记录到数组中)肯定会等于(同层)或小于(异层)后来的搜索状态。因此通过搜索状态的时间和对应位置的时间进行比较:若位置已走过,则其时间肯定比后来重复搜索此位置状态的时间要小,重复搜索此位置的状态不进入队列。由此可以避免走重复的位置以及可以取得最小路径。
当搜索的位置与终点的坐标相等,则结束。当队列为空,仍未找到终点,则是无路可达。
此题只是比平面迷宫多了个z轴的方向,如何解决呢?自然是可以建立一个30*30*30的三维的数组。但是那天刚好和梅州的一位朋友讨论了通过指针数组建立二维数组的问题,所以我决定动态建立三维数组:
int ***p;
void build( )
{
int i, j;
p = new int** [L];
for(i = 0; i < L; ++i)
{
p[i] = new int* [R];
}
for(i = 0; i < L; ++i)
{
for(j = 0; j < R; ++j)
{
p[i][j] = new int[C];
}
}
}
....
void del()
{
int i,j;
for(i = 0; i < L; ++i)
for(j = 0; j < R; ++j)
{
delete[]p[i][j];
}
for(i = 0; i < L; ++i)
{
delete[]p[i];
}
delete[]p;
}
在每个迷宫结束后delete了它,省点内存(有点好笑,27000算得了什么,但是当时觉得自己好劲【捂脸】, 菜鸡的奇妙想法)
好了,又要到挨批环节,辣鸡代码来了:
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
struct dot //结构体包含r,c,l三维坐标以及时间t
{
int l, r, c, t;
};
dot temp, now;
queue<dot> q; //队列
int ***p;
int L, R, C;
const int level[] = {-1,1,0,0,0,0}; //竖直方向
const int row[] = {0,0,-1,1,0,0}; //行方向
const int column[] = {0,0,0,0,-1,1}; //列方向
void build( ) //建立迷宫空间
{
int i, j;
p = new int** [L];
for(i = 0; i < L; ++i)
{
p[i] = new int* [R];
}
for(i = 0; i < L; ++i)
{
for(j = 0; j < R; ++j)
{
p[i][j] = new int[C];
}
}
}
void read() //读入迷宫
{
char c;
int i, j, k;
for(i = 0; i < L; ++i)
{
for(j = 0; j < R; ++j)
{
for(k = 0; k < C; ++k)
{
while(c = getchar()){if(c == '.' || c == '#' || c == 'S' || c == 'E') break;}
if(c == '#') p[i][j][k] = -1;
if(c == 'E') p[i][j][k] = -3;
if(c == '.') p[i][j][k] = 0;
if(c == 'S')
{
p[i][j][k] = -2;
temp.t = 0; temp.l = i; temp.r = j; temp.c = k;
q.push(temp); //将起点压入队列
}
}
}
}
}
void del() //delete[]
{
int i,j;
for(i = 0; i < L; ++i)
for(j = 0; j < R; ++j)
{delete[]p[i][j];}
for(i = 0; i < L; ++i)
{delete[]p[i];}
delete[]p;
}
int main()
{
while( scanf("%d%d%d", &L, &R, &C) == 3 && (L || R || C) )
{
int reach = 0;
build();
read();
while(!q.empty()) //队列为空则表示搜索完所有可行路径
{
now.l = q.front().l; now.r = q.front().r; now.c = q.front().c; now.t = q.front().t; //当前状态的坐标时间
q.pop();
for(int i = 0; i < 6; ++i) //对6个方向搜索
{
int ll = now.l + level[i]; int rr = now.r + row[i]; int cc = now.c + column[i]; int tt = now.t + 1;
if((ll < 0 || ll >= L) || (rr < 0 || rr >= R) || (cc < 0 || cc >= C)) continue;
if(p[ll][rr][cc] == -1 || p[ll][rr][cc] == -2) continue;
if(p[ll][rr][cc] != 0 && p[ll][rr][cc] <= tt && p[ll][rr][cc] != -3) continue; //排除各种不可行的搜索状态(-1阻碍物,-2起点,-3终点)
if(p[ll][rr][cc] == -3)
{
p[ll][rr][cc] = tt;
reach = 1;
printf("Escaped in %d minute(s).\n",p[ll][rr][cc]); break;
}
temp.l = ll; temp.r = rr; temp.c = cc; temp.t = tt;
p[ll][rr][cc] = tt;
q.push(temp); //将可行的搜索状态压入队列
}
if(reach == 1) break;
}
if(reach == 0) printf("Trapped!\n");
while (!q.empty()) q.pop(); //清空队列准备下一次迷宫
}
return 0;
}
这题是三维迷宫,而接下来的题目则是可以看成一维迷宫,完全可以沿用走迷宫的方法。方法和代码就不再赘述了。
- POj 3278 Catch That Cow
Description
Farmer John has been informed of the location of a fugitive cow and wants to catch her immediately. He starts at a point N (0 ≤ N ≤ 100,000) on a number line and the cow is at a point K (0 ≤ K ≤ 100,000) on the same number line. Farmer John has two modes of transportation: walking and teleporting.
* Walking: FJ can move from any point X to the points X - 1 or X + 1 in a single minute
* Teleporting: FJ can move from any point X to the point 2 × X in a single minute.
If the cow, unaware of its pursuit, does not move at all, how long does it take for Farmer John to retrieve it?
Input
Line 1: Two space-separated integers: N and K
Output
Line 1: The least amount of time, in minutes, it takes for Farmer John to catch the fugitive cow.
Sample Input
5 17
Sample Output
4
Hint
The fastest way for Farmer John to reach the fugitive cow is to move along the following path: 5-10-9-18-17, which takes 4 minutes.
#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
queue<int> q;
int main()
{
int line[100001];
int N, K;
int reach = 0;
scanf("%d%d",&N,&K);
if(N == K)
{printf("0"); return 0;}
memset(line,0,sizeof(line));
q.push(N);
while(!q.empty())
{
int now_x = q.front(); int now_t = line[now_x];
q.pop();
for(int i = 0; i < 3; ++i)
{
int x, t;
if(i == 0) x = now_x + 1;
else if(i == 1) x = now_x - 1;
else if(i == 2) x = now_x * 2;
t = now_t + 1;
if(x < 0 || x > 100000) continue;
if(x == N) continue;
if(x == K) {reach = 1; printf("%d",t); break;}
if(line[x] != 0 && line[x] < t) continue;
line[x] = t;
q.push(x);
}
if(reach == 1)
break;
}
return 0;
}
附:
二维迷宫(转载自scutTic-Search PPT)
谢谢观看!!!