CodeForces505 C. Mr. Kitayuta, the Treasure Hunter (推出数据变化范围+偏移存储,常数优化dp)

题意:

有编号为0到30000的小岛,总共30001个。
给n和d,表示有n颗宝石,你的初始跳跃长度为d
然后给出n个数p(i),表示p(i)位置有一颗宝石
开始你在岛0上,第一次跳跃只能跳过d个小岛
假设上一次你的跳跃距离为x,那么这一次你可以选择跳x、x+1、x-1,(要求跳x-1的时候x-1>=1,不能跳过30000)
当你不能再继续跳的时候停止,跳到一个带有宝石的岛上会收集岛上的宝石
现在问过程中最多收集到多少个宝石

数据范围:n<=3e4,d<=3e4,d<=p(i)<=3e4

解法:
朴素dp:
令d[i][j]为当前跳到i,上一次跳跃距离为j的最大宝石数量

但是题目的数据范围是3e4,二维的数组看不下,O(3e4^2)的算法跑不动

但其实第二维的跳跃范围没有3e4这么大
假设第一次跳d,第二次跳d+1,第三次跳d+2,这样跳跃距离不断增加
因为小岛只有3e4,设总共跳了x次,第x次时距离比初始情况增加(x-1)
那么可以计算出x*d+(x-1)*x/2<=3e4
要使得距离增加量最大,不妨令d为1,那么x+(x-1)*x/2<=3e4
稍微放大一下近似为x*x/2<=3e4,即x*x<=6e4,显然x<300
因此跳完全程距离的偏转值不会超过300,
即开始跳跃距离为d,全程的距离肯定在(d-300,d+300)之间
因此第二维只需要开600大小就行了

总复杂度也变为了O(3e4*600)
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=3e4+5;
const int N=30000;
int f[maxm][605];
int cnt[maxm];
int n,d;
signed main(){
    cin>>n>>d;
    for(int i=1;i<=n;i++){
        int x;cin>>x;
        cnt[x]++;
    }
    //将长度x偏移为x-d+300
    memset(f,-1,sizeof f);
    f[d][d-d+300]=cnt[d];
    for(int i=d;i<=N;i++){
        for(int j=0;j<=600;j++){
            if(f[i][j]==-1)continue;//无法到达的跳过
            int x=j+d-300;//真实长度
            if(i+x+0>i&&i+x+0<=N)f[i+x+0][j+0]=max(f[i+x+0][j+0],f[i][j]+cnt[i+x+0]);
            if(i+x+1>i&&i+x+1<=N)f[i+x+1][j+1]=max(f[i+x+1][j+1],f[i][j]+cnt[i+x+1]);
            if(i+x-1>i&&i+x-1<=N)f[i+x-1][j-1]=max(f[i+x-1][j-1],f[i][j]+cnt[i+x-1]);
        }
    }
    //
    int ans=0;
    for(int i=d;i<=N;i++){
        for(int j=0;j<=600;j++){
            ans=max(ans,f[i][j]);
        }
    }
    cout<<ans<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值