简单搜索训练(1)总结
这次的题目是简单的搜索题,主要是BFS和DFS。其中有新收获的知识点是BFS和DFS的记忆化搜索,以及算法题中树的结构的实现以及floyd算法的原理和实现。通过3个具体题目来记录知识点。
P1141 01迷宫(知识点:记忆化BFS)
大意:有一个nxn的迷宫,只由0和1组成。0只能往1走,1只能往0走,可以选择上下左右四个方向,问最多能走几格(包括自己)。
输入n,m(起点个数)
输入nxn的map
输入m个起点
输入输出样例
输入
2 2
01
10
1 1
2 2
输出
4
4
说明/提示
所有格子互相可达。
分析过程:如果暴力搜的话很容易得出答案,但是果断TLE了。必须采用记忆化搜索来优化时间。
如求图的连通性,到达点的数量问题,若点a能到点b,那么点b就一定能到点a,所以搜索a能走到的地点数和b能够走到的地点数是一样的。因此,如果已经搜索过点a,那么搜索b时就可直接调用搜索a的结果。
附上AC代码:
#include <bits/stdc++.h>
using namespace std;
string mapp[1001];
int vis[1001][1001];
int m,n;
int ans[100000]={0};
struct point
{
int x,y;
};
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
int BFS (int x,int y,int t)//t记录第t次搜索
{
//memset(vis,0,sizeof(vis));(在记忆化搜索中初始化可以不要,因为如果这个点之前搜索过,那么直接用其结果就行了)
//vis[x][y]=1;
int num=1;
queue<point> q;
q.push({x,y});
point cur;
char aim;
while (!q.empty())
{
cur=q.front();
q.pop();
if (mapp[cur.x][cur.y]=='0')
{
aim='1';
}
else aim='0';
int xx,yy;
for (int i=0;i<4;i++)
{
xx=cur.x+dx[i];
yy=cur.y+dy[i];
if (xx>=0&&xx<n&&yy>=0&&yy<n&&vis[xx][yy]==0&&mapp[xx][yy]==aim)
{
//cout<<"map is "<<mapp[xx][yy]<<" and vis is "<<vis[xx][yy]<<endl;
q.push({xx,yy});
vis[xx][yy]=t;//t用在这里
num++;
}
}
}
return num;
}
//因为点a和点b连通,因此ans(a)和ans(b)实际上是一样的,于是采用记忆化搜索,可以增加时间效率
int main ()
{
while (cin>>n>>m)
{
memset(vis,0,sizeof(vis));
for (int i=0;i<n;i++)
{
cin>>mapp[i];
}
int x,y;
for (int i=1;i<=m;i++)//第i次搜索
{
cin>>x>>y;
x=x-1;
y=y-1;
if (vis[x][y]==0)//没有访问过这个点
{
vis[x][y]=i;
ans[i]=BFS(x,y,i);
cout<<ans[i]<<endl;
}
else //之前已经访问过这个点了
{
int index=vis[x][y];//vis[x][y]的值等与第i次访问中的i等价
ans[i]=ans[index];//这一次搜索的结果等与之前那一次搜索的结果
cout<<ans[i]<<endl;
}
}
}
system("pause");
return 0;
}
由此可以总结出一个BFS的记忆化模板:
//BFS函数的改变
int BFS (int x,int y,int t)//多一个参数t,记录搜索的次序
{
...
while(!q.empty())
{
...
q.push({xx,yy});
vis[xx][yy]=t;//t用在这里,储存第一次搜到该点的次序
num++;
}
retur num;
}
//main函数的改变
int main ()
{
for (int i=1;i<=m;i++)//第i次搜索
{
cin>>x>>y;
if (vis[x][y]==0)//没有访问过这个点
{
vis[x][y]=i;
ans[i]=BFS(x,y,i);//ans[i]为第i次搜索的结果
cout<<ans[i]<<endl;
}
else //之前已经访问过这个点了
{
int index=vis[x][y];//vis[x][y]储存了这个点的第一次搜索的次序
ans[i]=ans[index];//这一次搜索的结果等与之前那一次搜索的结果
cout<<ans[i]<<endl;
}
}
return 0;
}
p1343滑雪(知识点:记忆化DFS)
题目大意:有一个nxm的map,在一个点上可以向上下左右四个方向的比自己值小的格子走。问这个map中最长能走的格子数是多少?
输入n,m;
输入map;
输出结果。
输入输出样例
输入
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
输出
25
分析:暴力法:对每一个点作为起点直接暴力搜最长路储存,然后找出最大值,理论可行但必TLE。假设以点a为起点,最多能走n格;之后以b为起点的时候会经过点a,那么因为点a能走的最大长度之前已经计算过了,因此储存起来直接用,能优化效率。这就是记忆化。
AC代码:
#include <bits/stdc++.h>
using namespace std;
int n,m;
int mapp[105][105];
int vis[105][105];//记录是否访问过以及当前点能滑的最大数
int ans[105][105];
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
int DFS(int x,int y)//返回从当前点开始滑能滑到的最远距离
{
if (vis[x][y]!=0) return vis[x][y];
int xx,yy;
for (int i=0;i<4;i++)
{
xx=x+dx[i];
yy=y+dy[i];
if (xx>=0&&xx<n&&yy>=0&&yy<m&&mapp[x][y]>mapp[xx][yy])
{
vis[x][y]=max(vis[x][y],DFS(xx,yy)+1);
}
}
return vis[x][y];
}
int main ()
{
while (cin>>n>>m)
{
memset(vis,0,sizeof(vis));
memset(ans,0,sizeof(ans));
for (int i=0;i<n;i++)
{
for (int j=0;j<m;j++)
{
cin>>mapp[i][j];
}
}
int maxans=0;
for (int i=0;i<n;i++)
{
for (int j=0;j<m;j++)
{
maxans=max(maxans,DFS(i,j)+1);
}
}
cout<<maxans<<endl;
}
system("pause");
return 0;
}
可以总结出记忆化DFS模板:
//DFS函数的改变
int DFS (int x,int y)//多一个参数t,记录搜索的次序
{
if (vis[x][y]!=0) return vis[x][y];//vis[x][y]中储存了点(x,y)能走的最大长度
for (int i=0;i<4;i++)
{
xx=x+dx[i];
yy=y+dy[i];
if (点合法)
{
vis[x][y]=max(vis[x][y],DFS(xx,yy)+1);
//因为求的是最长路,所以选取四个方向中的最长路
//并加上自己即可
}
}
return vis[x][y];
}
//main函数的改变
int main ()
{
//任然是对每一个点搜,但是记忆化优化了时间
for (int i=0;i<n;i++)
{
for (int j=0;j<m;j++)
{
maxans=max(maxans,DFS(i,j)+1);
}
}
return 0;
}
P1364 医院设置(知识点:算法中树结构的表示;floyd最短路算法)
题目描述
设有一棵二叉树,如图:
其中,圈中的数字表示结点中居民的人口。圈边上数字表示结点编号,现在要求在某个结点上建立一个医院,使所有居民所走的路程之和为最小,同时约定,相邻接点之间的距离为 11。如上图中,若医院建在1 处,则距离和 =4+12+2\times20+2\times40=136=4+12+2×20+2×40=136;若医院建在 33 处,则距离和 =4\times2+13+20+40=81=4×2+13+20+40=81。
输入格式
第一行一个整数 nn,表示树的结点数。
接下来的 nn 行每行描述了一个结点的状况,包含三个整数 w, u, vw,u,v,其中 ww 为居民人口数,uu 为左链接(为 00 表示无链接),vv 为右链接(为 00 表示无链接)。
输出格式
一个整数,表示最小距离和。
输入输出样例
输入
5
13 2 3
4 0 0
12 4 5
20 0 0
40 0 0
输出
81
思路:在树的结构基础上,把每一个点都尝试设置为医院,依次比较求出∑w[i]*len的最小值即可。
首先,因为树实际上也是一种图,因此可以用邻接矩阵表示。
其次是floyd算法(插点发求多源点之间的最短路径):
(此处有一个疑问,我把无边标记换成-1会得出某个计算结果,但是换成大数字(此处MAX为0x3f3f3f3f)就能得出正确答案,没有想明白是为什么)
void floyd(int n)
{
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
if (g[i][k]!=MAX&&g[k][j]!=MAX&&g[i][k]+g[k][j]<g[i][j])//MAX为无边标记
g[i][j]=g[i][k]+g[k][j];
}
}//函数结束后每个g[i][j]都存储了i到j的最短路径
设医院在节点i,通过floyd算法我们可以知道节点j到i的最短路径,那么
int ans(int n)
{
int res=0x3f3f3f3f;
int tmp;
for (int i=1;i<=n;i++)//医院安在i处
{
tmp=0;
for (int j=1;j<=n;j++)//求出每个点到医院i的距离和
{
tmp+=g[i][j]*weight[j];
}
res=min(res,tmp);
}
return res;
}
可以求出所要的答案。
AC代码:(注意二叉树的领接矩阵表示法)
#include <bits/stdc++.h>
using namespace std;
#define MAX 0X3F3F3F3F//换成-1就不行,不知道为什么
int g[105][105];//g[i][j]储存了从i到j的最短距离
int weight[105];
void floyd(int n)//floyd算法,用插点发求两点之间最短路径的算法
{
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
if (g[i][k]!=MAX&&g[k][j]!=MAX&&g[i][k]+g[k][j]<g[i][j])
g[i][j]=g[i][k]+g[k][j];
}
}//函数结束后每个g[i][j]都存储了i到j的最短路径
int ans(int n)
{
int res=0x3f3f3f3f;
int tmp;
for (int i=1;i<=n;i++)//医院安在i处
{
tmp=0;
for (int j=1;j<=n;j++)//求出每个点到医院i的距离和
{
tmp+=g[i][j]*weight[j];
}
res=min(res,tmp);
}
return res;
}
int main ()
{
int n;
while (cin>>n)
{
int w,l,r;
memset(g,MAX,sizeof(g));//-1表示彼此之间没有通路
for (int i=1;i<=n;i++)
{
cin>>w>>l>>r;
g[i][i]=0;//自己到自己的距离为0
g[i][l]=g[l][i]=1;
g[i][r]=g[r][i]=1;//到自己左右孩子的距离为1
weight[i]=w;//储存第i个点的权值
}
floyd(n);
cout<<ans(n)<<endl;
}
return 0;
system("pause");
}
ACM中各种用法确实与数据结构中有所差别,在今后的学习中应当多参考学习优秀的代码,利用更优秀的结构解决问题!