- 写法借鉴某个博主,本来想用线段树优化建边写这道题,写道后来才发现需要拆点,加上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();
}
}
}