P9065 [yLOI2023] 云梦谣 题解

文章描述了一个编程竞赛题目,涉及在一个由高度定义的网格中,主角朵一寻找从$(1,1)$到$(n,m)$的最短路径。朵一可以移动、飞行或改变格子高度,目标是最小化时间。解题策略包括使用BFS寻找不使用飞行的最短路,以及找到起点和终点附近的飞行点集合,通过双重循环寻找最优解。
摘要由CSDN通过智能技术生成

[yLOI2023] 云梦谣

题目背景

归来且做云梦梦一场 大梦好
栽花闻酒香 醒醒醉醉笑笑
天地偌大复路远山高 最难得偷半日逍遥
偶尔糊涂不问世事不知晓

——银临 & 慕寒《云梦谣》

题目描述

“喂,枸杞,你这只笨狗,又偷吃!看我不收拾你!”

朵一气呼呼地从院子里跑出来,手中握着掸子,而枸杞早已不见踪影。

云梦庭可以看作一个 $n$ 行 $m$ 列的方格阵,第 $i$ 行第 $j$ 列的格子被记作 $(i,j)$。每个格子 $(i,j)$ 要么有一个高度 $h_{i,j}$($h_{i,j}$ 为正整数),要么是障碍物,不能通过。(方便起见,约定障碍物的 $h_{i,j}$ 用 $0$ 表示。)另外,云梦庭上有 $k$ 个指定的格子上可以进行御剑飞行。开始时,朵一和枸杞分别位于方格 $(1,1)$ 和 $(n,m)$。

朵一的御剑飞行还不是很熟练,现在她还控制不好御剑的高度。因此在任意时刻,朵一在方格 $(i,j)$ 上可以做如下行动之一:

  • 移动到与该方格相邻的方格 $(i-1,j)$、$(i+1,j)$、$(i,j-1)$、$(i,j+1)$ 之一上(不能移动出方格边界,也不能移动到障碍物上);

  • 如果方格 $(i,j)$ 上允许御剑飞行,则朵一可以御剑飞行至另一个同样允许御剑飞行且与方格 $(i,j)$ 高度相等的方格上

  • 使用仙法将当前格子的高度 $h_{i,j}$ 改变为任一正整数。

进行上述每项行动均需花费 $1$ 个单位时间。

“哼,笨狗子你再跑!”说罢,朵一便追了出去。朵一接下来还要尽快继续今天的修行,因此她想知道到达 $(n,m)$ 格子所需的最短时间是多少。

输入格式

输入的第一行有三个整数,依次表示方格阵的行数 $n$、列数 $m$ 和能御剑飞行的方格个数 $k$。

接下来 $n$ 行,每行 $m$ 个整数,其中第 $i$ 行的第 $j$ 个数表示方格 $(i,j)$ 的高度 $h_{i,j}$。数据保证 $h_{1,1}$ 和 $h_{n,m}$ 不为 $0$。

接下来 $k$ 行,每行两个整数 $x$ 和 $y$,表示一个允许御剑飞行的方格的坐标 $(x, y)$。数据保证这 $k$ 个方格的坐标互不相同。

输出格式

一行一个整数,表示朵一到达 $(n,m)$ 所需的最小时间。如果朵一无法到达,输出 -1。

样例 #1

样例输入 #1

4 4 2
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
1 1
3 4

样例输出 #1

3

样例 #2

样例输入 #2

4 4 3
1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4
1 1
2 4
4 1

样例输出 #2

4

样例 #3

样例输入 #3

2 5 0
1 0 3 3 4
2 3 4 0 5

样例输出 #3

7

样例 #4

样例输入 #4

4 4 3
1 1 1 0
1 1 0 1
1 0 1 1
0 1 1 1
1 1
2 1
3 3

样例输出 #4

3

提示

样例 1 解释

第 $1$ 个单位时间,朵一将当前方格 $(1,1)$ 的高度修改为 $4$;

第 $2$ 个单位时间,朵一从方格 $(1,1)$ 御剑飞行至 $(3,4)$;

第 $3$ 个单位时间,朵一从方格 $(3,4)$ 移动到 $(4,4)$,追上了枸杞。

样例 2 解释

第 $1$ 个单位时间,朵一从方格 $(1,1)$ 御剑飞行至 $(4,1)$;

第 $2$ 个单位时间,朵一从方格 $(4,1)$ 移动到 $(4,2)$;

第 $3$ 个单位时间,朵一从方格 $(4,2)$ 移动到 $(4,3)$;

第 $4$ 个单位时间,朵一从方格 $(4,3)$ 移动到 $(4,4)$,追上了枸杞。

数据规模与约定

对全部的测试点,保证 $1 \leq n, m \leq 3 \times 10^3$,$0 \leq k,h_{i,j} \leq n \times m$。

提示

请注意大量数据读入对程序效率造成的影响,选择合适的读入方式,避免超时。

说明

本题共有 5 个附加样例文件,见附件里的 dream.zip。

后记

不过,别看朵一现在一副生气的样子,可当她追上枸杞后,大抵是不舍得真的动手吧。“嘿嘿,今日的修行结束后,该吃什么好呢?”在这飞瀑悬挂、翠竹怀抱的云梦庭中,修仙炼体,不羡尘嚣,应是这世上最逍遥的事了。


思路分析

由于可以御剑飞行的点之间均可以实现飞行抵达,很容易贪心得出:最多只使用一次御剑飞行;使用御剑飞行的出发点和到达点分别应是距离起始点、终点最近的点 (可能有多个);御剑飞行出发点和到达点的高度若可以相同,则选择相同高度的。

先考虑不使用御剑飞行的情况,直接用bfs求最短路。

再考虑御剑飞行,用bfs分别找到距离起点、终点最近的点的集合,再用二重循环暴力枚举,不断更新最小时间。


代码

#include<iostream>
#include<cstdio>
#include<stack>
#include<queue>
#include<algorithm>
#include<vector>
using namespace std;
int n,m,k,h[3010][3010],t[3010][3010],dx[4]={1,-1,0,0},dy[4]={0,0,1,-1},dis1[3010][3010],dis2[3010][3010];
bool fly[3010][3010];
struct Point{
    int x,y;
};
queue<Point> q;
vector<Point> v1,v2;
void read(){
    //cout<<1;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++) scanf("%d",&h[i][j]);
    //cout<<2;
    for(int i=0;i<k;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        fly[x][y]=true;
    }
}
bool Move(int x,int y){
    return x>0&&x<=n&&y>0&&y<=m&&h[x][y]!=0;
}
void clear_q(queue<Point>& q){
    while(!q.empty()) q.pop();
}
void bfs(){//不使用御剑飞行所到达每个点的用时
    clear_q(q);
    q.push({1,1});
    while(!q.empty()){
        int x_now=q.front().x,y_now=q.front().y;
        q.pop();
        for(int i=0;i<4;i++){
            if(Move(x_now+dx[i],y_now+dy[i])&&t[x_now+dx[i]][y_now+dy[i]]==0) {
                t[x_now+dx[i]][y_now+dy[i]]=t[x_now][y_now]+1;
                q.push({x_now+dx[i],y_now+dy[i]});
            }
        }
    }
}
void bfs_fly(int x0,int y0,vector<Point>& v,int (&dis)[3010][3010]){//搜索距离(x0,y0)最近的御剑飞行点,放置到v中
    clear_q(q);
    q.push({x0,y0});
    bool find=false;
    while(!q.empty()){
        int x_now=q.front().x,y_now=q.front().y;
        q.pop();
        if(fly[x_now][y_now]) {
            v.push_back({x_now,y_now});
            find=true;
        }
        for(int i=0;i<4;i++){
            if(Move(x_now+dx[i],y_now+dy[i])&&!find&&dis[x_now+dx[i]][y_now+dy[i]]==0) {
                q.push({x_now+dx[i],y_now+dy[i]});
                dis[x_now+dx[i]][y_now+dy[i]]=dis[x_now][y_now]+1;
            }
        }
    }
}
void solve(){
    cin>>n>>m>>k;
    read();
    bfs();
    bfs_fly(1,1,v1,dis1);
    bfs_fly(n,m,v2,dis2);
    int ans=t[n][m];
    for(int i=0;i<(int)v1.size();i++)
    for(int j=0;j<(int)v2.size();j++){
        int t1=h[v1[i].x][v1[i].y]==h[v2[j].x][v2[j].y]?dis1[v1[i].x][v1[i].y]+dis2[v2[j].x][v2[j].y]+1:dis1[v1[i].x][v1[i].y]+dis2[v2[j].x][v2[j].y]+2;
        ans=ans==0?t1:min(ans,t1);
    }
    if(ans>0) cout<<ans;
    else cout<<-1;
}
int main(){
    solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值