简单搜索训练(1)总结

简单搜索训练(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中各种用法确实与数据结构中有所差别,在今后的学习中应当多参考学习优秀的代码,利用更优秀的结构解决问题!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青缘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值