充电桩的收益(合肥市第39届信息学竞赛(2022年))

一、题目:

题目描述 Description

小可可在小区里安装了一个电动汽车充电桩,将自家充电桩的空闲时间开放给其他电动车用户付费使用。这种共享充电模式能充分提高闲置充电桩的利用率,既可以让小可可获得收益,也缓解了其他车主的充电焦虑。现在共有n个使用充电桩的申请,编号从0到n-1.小可可将按编号顺序依次处理所有申请,每个申请Qi(0<=i<=n-1)信息包含两个正整数ai和bi。
对于申请Qi小可可有两种处理策略:
(1)接受申请Qi,将获得ai元收益,但必须放弃接下来的bi个申请。
(2)拒绝申请Qi,没有收益,继续处理下一个申请。
请帮助小可可计算出共享充电桩的能获得的最大收益。

输入描述 Input Description

输入共n+1行;
第1行:一个整数n,表示使用充电桩的申请数量;
第2行到第n+1行:每行两个整数ai和bi;
表示接受申请Qi,将获得ai元收益,但必须放弃接下来的bi个申请。

输出描述 Output Description

输出共1行,一个正整数,表示小可可共享充电桩获得的最大收益。

样例输入 Sample Input

4 3 2 5 4 4 4 3 5

样例输出 Sample Output

6

数据范围及提示 Data Size & Hint

样例解释:
小可可共收到4个使用充电桩的申请,最佳策略为接受申请0和申请3。
(1)接受申请0,获得3元收益,但接下来的两个申请必须拒绝;
(2)接受申请3,获得三元收益;
总收益为:3元+3元=6元

数据范围:

1<=n<=1000000,1<=ai,bi<=100000;
20%数据:1<=n<=20 1<=ai<=500 1<=bi<=10
40%数据:20<=n<=2000 500<=ai<=20000 10<=bi<=100
60%数据:2000<=n<=100000 20000<=ai<=50000 100<=bi<=200
100%数据:100000<=n<=1000000 50000<=ai<=100000 200<=bi<=2500

二、分析:

看题目,100%数据 最多有1000000个申请,每个申请收益最多100000,最少跳过200个申请。

可以搜索!

搜索函数里,注意判断这条申请是否超过了申请个数。如果是,返回0

否,在"放弃,查看下一个申请" 和 "接受,获得a[i]收益,跳过b[i]中取最大值"中取最大值。

代码:

#include<iostream>
#include<cstring>
using namespace std;
const int MAX=1000005;
int n,a[MAX],b[MAX];//a[]:收益,b[]:放弃申请个数
int f(int x){//搜索
    if(x>n) return 0;//超出
    return max(f(x+1),f(x+b[x]+1)+a[x]);//f(x+1)是放弃当前申请,f(x+b[x]+1)+a[x]是接受申请
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i],&b[i]);
    }
    cout<<f(1);//从1开始
    return 0;
}

但是……

WHY?

100%数据:100000<=n<=1000000 50000<=ai<=100000 200<=bi<=2500

OH,原来n最大是 1000000,递归肯定超时!

怎么办呢?

下面隆重登场的是——记忆化搜索!

啥是记忆化搜索?

记忆化搜索可以将搜索时一些不必要的运算“剪掉”。

在这题中,f(x+1),f(x+b[x]+1)可能已经算过。

所以我们把算过的记录。

若递归调用到算过的,把记录的值输出就行了!

代码:

#include<iostream>
#include<cstring>
using namespace std;
const int MAX=1000005;
int n,a[MAX],b[MAX],w[MAX];//w数组是记忆化数组
int f(int x){
    if(x>n) return 0;//边界
    if(w[x]!=-1) return w[x];//已经算过了
    return w[x]=max(f(x+1),f(x+b[x]+1)+a[x]);
}
int main(){
    cin>>n;
    memset(w,-1,sizeof(w));//清为-1,因为题目中返回值可能是0
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i],&b[i]);
    }
    cout<<f(1);//顺推
    return 0;
}

方2:

动态规划

可以倒推

#include<iostream>
using namespace std;
int a[1000005],b[1000005],f[1010005],n;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i],&b[i]);
    }
    for(int i=n;i>=1;i--){
        f[i]=max(f[i+1],f[i+b[i]+1]+a[i])//跟记忆化函数一样的功能,不用剪掉无用运算
    }
    cout<<f[1];
    return 0;
}

AC咯!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值