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

题意

给定一口深度为n的井,和两个个长度为n的数组a和b(下标从1~n),a[i]表示你可以再高度i的位置往上跳a[i]高度,b[i]表示如果你跳到i这个位置会往下滑b[i]的高度,求从井底跳到深度0的位置最少需要多少步,并且输出跳的方案。

思路

看思路之前首先看数据范围,1≤n≤300000,0≤a[i]≤i,0≤b[i]≤n-i。
比较容易能想到一种dp的思路,设dp[i]为从井底跳到高度i所需的最少步数,设你当前位置为x,x
高度所有可以跳到的地方yi(跳到一个高度之后滑下来的地方),都可以有一个转移方程dp[yi]=min(dp[yi],dp[x]+1),首先令dp[n]=0,然后从深度n开始跑bfs,更新dp数组就好了,这样复杂度为O(N^2),对于300000的数据范围来说肯定是不行的。其实仔细观察一下我们跳的过程,对于两个高度x1和x2,我们要更新他们俩能跳到的地方可能会重复求解同一段区间,首先x1能判断出高度[x1-a[x1],x1)中那些跳的到那些跳不到,x2能判断出高度[x2-a[x2],x2)中那些跳的到那些跳不到,重复求解的部分就是这两个区间重叠的部分,所以我们这里用bfs跑最短路的方式从深度n往上跑,如果某个高度x跑过之后x-a[x]以后的位置以后就不用跑了,也就是x判断过的高度以后就不需要判断了,实在还是无法理解可能我讲的还不太简单明了,具体看代码内。

#include<bits/stdc++.h>
using namespace std;
const int inf=999999999;
int a[300005],b[300005],v[300005],dp[300005],pre[300005],vis[300005];
//pre存储跳到这个节点的前一个节点是哪个节点,v存储那个节点滑倒这个节点(x跳到i滑倒y是一次跳跃)
int ans[300005];
int tot=0;
int n;
void bfs(int x)//bfs从井底往上跳
{
    for(int i=0;i<=n;i++)
    dp[i]=inf,pre[i]=i;
    dp[x]=0;
    int mx=inf;
    queue<int>q;
    q.push(x);
    while(q.size())
    {
        if(vis[0])
        break;
        x=q.front();
        q.pop();
        int s=max(0,x-a[x]);
        int e=min(mx,x);//(mx~n)都已经被判断过了
        for(int i=s;i<=e;i++)
        {
            if(vis[0])
            break;
            int y=i+b[i];
            if(vis[y])
            continue;
            if(dp[y]>dp[x]+1)
            {
                dp[y]=dp[x]+1;//更新dp值
                pre[y]=x;//x跳到i滑到y
                v[y]=i;//i滑到y
            }
            vis[y]=1;
            q.push(y);
        }
        mx=min(mx,x-a[x]);
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    scanf("%d",&b[i]);
    bfs(n);
    if(dp[0]==inf)
    {
        printf("-1\n");
    }else
    {
        printf("%d\n",dp[0]);
        int p=0;
        while(pre[p]!=p)
        {
            ans[++tot]=v[p];
            p=pre[p];
        }
        for(int i=tot;i>=1;i--)
        {
            printf("%d ",ans[i]);
        }
        printf("\n");
    }
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值