讨厌的青蛙

题目与思路均来自《算法基础与在线实践》这本书,博客主要为了记录一下并且总结自己的问题

题目很长,直接放链接讨厌的青蛙

要求出最长的路径,就要比较所有的路径长度。对于每一条路径,因为步长相等,所以只要确定开始两个被踩的点就可以求出整条路径了。假设前两个点为(x1,y1),(x2,y2),则步长dx=x2-x1,dy=y2-y1,需要判断下面三个条件是否都满足。

  1. 之后的每个点(xi,yi)=(x(i-1)+dx,y(i-1)+dy)=(x2+(i-2)*dx,y2+(i-2)*dy)
  2. (x1-dx,y1-dy)需要落在稻田之外
  3. 将路径上的最后一棵水稻记作(xk,yk),则(xk+dx,yk+dy)需要落在稻田之外

因此,程序只需要枚举前两个点,然后判断整条路径是否存在并判断长度是否大于当前最大值,即可。判断一个点是否被踩过,可以用一个布尔矩阵。布尔矩阵一开始就存好各个点的状态,true表示被踩过,false表示没有被踩过。这样之后判断可以用O(1)的效率确定一个点是否被踩过。

由于时限要求,实现时要注意一些条件的判断,以提高效率。例如,当下列条件之一满足时,当前枚举的两个点的路径就不满足最长路径。

  1. 青蛙不能经过一跳从稻田外跳到(x1,y1)上(此时一定不满足条件)。
  2. 按照(x1,y1),(x2,y2)确定的步长,从(x1,y1)出发,青蛙经过(MAXSTEPS-1)步,就会跳到稻田外,其中MAXSTEPS就是当前已经找到的最好答案(此时即使满足条件,答案也不会更优)。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

#define MAXN 5000+10
struct Point{
    int x,y;
};
int ans=0;//答案,即最大被踩水稻数
bool flag[MAXN][MAXN];
Point p[MAXN];
int r,c;
bool outside(int x1,int y1)
{
    if(x1<=0||x1>r||y1<=0||y1>c)
        return true;
    else return false;
}
bool operator <(const Point &a,const Point &b)
{
    return(a.x<b.x||(a.x==b.x&&a.y<b.y));
}
bool count(int a,int b)
{
    int dx=p[b].x-p[a].x;
    int dy=p[b].y-p[a].y;
    //不能一步从稻田外跳到(x1,y1x)上,排除
    if(!outside(p[a].x-dx,p[a].y-dy))
        return false;
            //如果经过ans步跳了出去,一定不会更新ans值
        if(outside((p[a].x+ans*dx),(p[a].y+ans*dy)))
           return false;
        int k=2;
        int x1=p[b].x+dx;
        int y1=p[b].y+dy;
        //当下一步还在稻田里并且存在,步长加1
        while(!outside(x1,y1)&&flag[x1][y1]){
            k++;
            x1+=dx;
            y1+=dy;
        }
        //跳出上个循环的条件为最后一步在稻田外才能算一个路径
   if(outside(x1,y1)&&k>ans)
        ans=k;
}
int main()
{
    //freopen函数可以直接从文件中读取数据
    //freopen("讨厌的青蛙.txt","r",stdin);
    int n;
    cin>>r>>c>>n;
    for(int i=0;i<n;i++){
        cin>>p[i].x>>p[i].y;
        flag[p[i].x][p[i].y]=true;
    }
    //排序可以提高效率
    //每种可行的路径都既可以从小到大跳过去,也可以从大到小跳
    sort(p,p+n);
    for(int i=0;i<n;i++)
        for(int j=i+1;j<n;j++)
        count(i,j);
    if(ans<3)
        ans=0;
    cout<<ans<<endl;
    return 0;
}

实现技巧:

  1. 可以将稻田存储为矩阵,从而快速判断一个点是否被踩过。考虑到空间要求,矩阵可以使用bool类型,从而避免超出内存空间限制。
  2. 注意以下条件可以提高时间效率。
  • 一开始对所有点按位置排序,这样枚举只用枚举(n-1)*n/2对起始点即可。
  • 枚举两个起点后,加入一些预判条件,如问题分析中的两个条件,判断第一只青蛙能否一步从稻田外跳入、当前路径的最大长度是否大于当前最大值等。

练习时出现的一些问题:

1.刚开始不懂排序的作用,尝试直接去掉排序

for(int i=0;i<n;i++)
        for(int j=i+1;j<n;j++)
        count(i,j);

这样枚举,答案一直错误,后来发现这样不能列举完所有情况

而下面这样枚举又会超时

for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
        count(i,j);

对于任何一条可行的路径,青蛙可以从大坐标往小坐标跳,也可以从小坐标往大坐标跳而把p数组排序后,所以排序后再枚举就可以大大减少枚举次数。

2.题目要考虑的因素太多,总是忘记一些

while(!outside(x1,y1)&&flag[x1][y1]){
            k++;
            x1+=dx;
            y1+=dy;
        }

路线的最后一个点必须能一步跳到稻田外,所以累加到最后的那个点不仅要满足不在被踩点的范围里,还要满足!outside(x1,y1)

一开始忘记判断累加点是否被踩过,写成了这个样子

while(!outside(x1,y1)){
            k++;
            x1+=dx;
            y1+=dy;
        }

以至于下一个if函数始终不理解判断点是否在稻田外的做法

if(outside(x1,y1)&&k>ans)
        ans=k;

猜你喜欢

转载自blog.csdn.net/qq_40980884/article/details/83184567