Codeforces Round #751 (Div. 2) D. Frog Traveler

  • 写法借鉴某个博主,本来想用线段树优化建边写这道题,写道后来才发现需要拆点,加上3e5的范围,线段树建边空间可能不太够,本身初始就是2nlogn的边数(两颗线段树*nlogn),拆点继续乘2,那么不算上后续加边的话就是至少4nlogn的边,差不多就是7e多的边,感觉不太妙,写着也麻烦,于是换了bfs写法。
  • bfs写法基于贪心,原理是我们在某个点时,可以选择0~a[i]的距离走,那么我们选择的必定是最利于我们的,后续我们就不需要再这些范围内遍历了,所以我们可以设定一个值,代表我们之前最高可以到达什么位置,后续遍历时若低于这个位置,那么就剪枝掉,由于0~n内每个高度最多被遍历一次,因此复杂度为O(n)。
  • 会不会有可能再次回到之前能取的范围呢?不会的,如果我们第二次需要回到之前能取的范围才能使答案更优,那么我们之前能取为什么不取?
  • 坑点有很多,之前想用结构体队列跑bfs,结构体内嵌套一个vector存路径,结果tle17了,应该是结点太多,mle显示tle了吧(应该不是真的tle,毕竟On),因此我们要想办法优化获取答案的过程。
  • 不能用vector直接存,我们只能像链表一样存储每个位置之前是哪个位置来获取答案,这里我们要用滑下去之后的位置来当作跳板,因为这样我们可以直接遍历到n这个位置,由于我们可以在n直接跳,因此n就相当于刚刚滑下去的状态。但是这个不能直接作为答案,答案需要输出跳上去时的高度,那么我们就记录每次滑下去之前的高度是什么就可以了。注意答案的输出顺序,我们是从井口开始遍历的,而答案要求输出从井底遍历的结果,因此我们使用stack倒置答案。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <math.h>
#include <map>
#include <set>
#include <queue>
#include "stack"
using namespace std;
#define endl '\n'
const int maxn = 3e5 + 5;
int dis[maxn],ans[maxn],pre[maxn];
int a[maxn],b[maxn],n;
int bfs(){
   queue<int> q;
   memset(dis,0x3f,sizeof dis);
   dis[n] = 0;
   q.push(n);
   int minn = n;
   while (!q.empty()){
       int now = q.front();q.pop();
       if (now == 0)   return dis[now];
       for (int i = now - a[now]; i <= now; ++i) {
           if (i >= minn) break;
           int to = i + b[i];
           if (dis[to] > dis[now] + 1){
               q.push(to);
               pre[to] = now;
               dis[to] = dis[now] + 1;
               ans[to] = i;
           }
       }
       minn = min(minn,now - a[now]);
   }
   return -1;
}
int main(){
   std::ios::sync_with_stdio(false);
   cin.tie(0); cout.tie(0);
   cin >> n;
   for (int i = 1; i <= n; ++i) {
       cin >> a[i];
   }
   for (int i = 1; i <= n; ++i) {
       cin >> b[i];
   }
   int res = bfs();
   if (res < 0)    cout << -1;
   else{
       cout << dis[0] << endl;
       stack<int> s;
       int p = 0;
       while (p != n){
           s.push(p);
           p = pre[p];
       }
       while (!s.empty()){
           cout << ans[s.top()] << ' ';
           s.pop();
       }
   }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值