洛谷 P3957 跳房子

博客分析了洛谷P3957跳房子问题的解决方案,利用二分查找优化计算,在不超过一定金币花费的情况下求解最大数字和。通过动态规划(DP)和单调队列技术,实现高效计算,解决了在数据范围较大的情况下可能导致的时间限制问题。
摘要由CSDN通过智能技术生成

题目链接

分析:

我们发现,当我们花费a枚金币对机器人进行改造时,若能拿到最大数字和z,那么显然当我们花费b(b>a) 枚金币也必然能得到数字和z甚至更大,至少采用和花费a时相同的走法就能保证最大数字和不会降低。
所以我们决定采用二分的算法,理论上二分的上限为xi的大小,即109,但由于数据过水(虽然没给出)保证了相邻两点相距不超过1000,故设二分上限为1000即可,二分下限显然为0

最大数字和=F(花费金币)这个函数不递减,所以我们可以采用二分法来查找最小花费金币数。

剩下的问题就是如何计算在确定花费的前提下计算出最大数字和(或者判断能否超过k)。
由于每次跳跃必须落到格子上,所以可以使用DP求解,dp[i] 可由递推式 dp[i] = max{ dp[i],max{ dp[j] } + s[i] }(dis( i , j ) 在步幅内) 来求出。

代码如下

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

typedef long long LL;

bool check(LL x[],int s[],int n,int d,LL k,int cost)
{
    int minn=d-cost,maxx=d+cost;
    LL dp[n+1];
    memset(dp,0x80,sizeof(dp));
    dp[0]=0;
    for(int i=1; i<=n; i++)
    {
        for(int j=i-1; j>=0; j--)
        {
            if(x[i]-x[j]<minn)continue;
            if(x[i]-x[j]>maxx)break;
            dp[i]=max(dp[i],dp[j]+s[i]);
        }
        //cout<<"dp["<<i<<"]="<<dp[i]<<endl;
        if(dp[i]>=k)
            return true;
    }
    return false;
}

int main()
{
    //input
    int n,d;
    LL k;
    cin>>n>>d>>k;
    LL x[n+1];
    int s[n+1];
    x[0]=0;
    for(int i=1; i<=n; i++)
        cin>>x[i]>>s[i];

    //binary search
    int ans=-1;
    //双开区间
    int lt=-1,rt=1001;
    while(lt<=rt-2)
    {
        int mid=(lt+rt)>>1;
        if(check(x,s,n,d,k,mid))
        {
            //cout<<mid<<" is OK"<<endl;
            ans=mid;
            rt=mid;
        }
        else
        {
            //cout<<mid<<" is not OK"<<endl;
            lt=mid;
        }
    }

    //output
    cout<<ans<<endl;
    return 0;
}

题外话

虽说以上代码就能AC了,但其实是由于数据有隐含的特性,若数据按题目所述,二分则需要从 0到109 ,也就是代码需要改成

int lt=-1,rt=1000000001;

这样就会导致TLE。
所有求区间最值问题都可以用单调队列来优化,这题也不例外,优化为单调队列后的代码即使二分从 0到109 也不会TLE。

终极代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

typedef long long LL;

bool check(LL x[],int s[],int n,int d,LL k,int cost)
{
    int minn=d-cost,maxx=d+cost;

    //monotone queue
    LL mq[n+1];
    int mq_id[n+1];
    int ft=0,rr=0;//front,rear

    LL dp[n+1];
    memset(dp,0x80,sizeof(dp));
    dp[0]=0;
    int j=0;
    for(int i=1; i<=n; i++)
    {
        while(j<i&&x[i]-x[j]>=minn)
        {
            while(ft<rr&&dp[j]>=mq[rr-1])rr--;
            mq[rr]=dp[j];
            mq_id[rr++]=j;
            j++;
        }
        while(ft<rr&&x[i]-x[mq_id[ft]]>maxx)ft++;
        if(ft<rr)dp[i]=max(dp[i],mq[ft]+s[i]);

        //cout<<"dp["<<i<<"]="<<dp[i]<<endl;
        if(dp[i]>=k)
            return true;
    }
    return false;
}

int main()
{
    //input
    int n,d;
    LL k;
    cin>>n>>d>>k;
    LL x[n+1];
    int s[n+1];
    x[0]=0;
    for(int i=1; i<=n; i++)
        cin>>x[i]>>s[i];

    //binary search
    int ans=-1;
    //双开区间
    int lt=-1,rt=1000000001;
    while(lt<=rt-2)
    {
        int mid=(lt+rt)>>1;
        if(check(x,s,n,d,k,mid))
        {
            //cout<<mid<<" is OK"<<endl;
            ans=mid;
            rt=mid;
        }
        else
        {
            //cout<<mid<<" is not OK"<<endl;
            lt=mid;
        }
    }

    //output
    cout<<ans<<endl;
    return 0;
}

分类

二分、DP、单调队列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值