小奇探险

题目

题目描述

小奇去遗迹探险,遗迹里有  个宝箱,有的装满了珠宝,有的装着废品。

小奇有地图,所以它知道每一个宝箱的价值,但是它不喜欢走回头路,所以要按顺序拿这  个宝箱中的若干个。

拿宝箱很累的。一开始小奇的体力是 ,每得到一个宝箱之后,小奇得到的价值是体力  宝箱的价值,之后它的体力就会变为原来的  倍 。

小奇不喜欢连续放过很多宝箱,所以任意一段长度为  的序列中,小奇一定要取走其中的一个宝箱。

现在小奇想知道它能得到的最大价值和。

输入格式

第一行,两个整数 ,表示的含义如题目中所述;

第二行,一个小数 ,表示的含义如题目中所述,最多  位小数;

第三行, 个整数,第  个整数表示第  个宝箱的价值。

输出格式

输出一行,一个实数,表示小奇能得到的最大价值和,四舍五入保留两位小数。

样例

样例输入

3 2
0.1
1 2 3

样例输出

2.30

 

题解&分析

首先不考虑k的情况,那么就是一个裸的dp+单调队列优化

直接一维枚举即可

可是这里会存在k,那么这个k有什么影响呢?

首先,如果我们还是用一维数组,它不满足dp的基本性质:无后效性

也就是说,前面选的个数是会影响后面的价值,可是正常dp后面的点是不能被影响的

所以就有两种解决方案:

第一种是直接将它扩大成二维

dp[i][j]表示到第i个点为止已经选了j个作为宝藏

那么dp[i][j] = dp[k][j-1] + k_{j-1}*a[i]

即使用了滚动数组与优化,也是n平方的时间复杂度

 

那么还有一种方法,就是从后往前去dp,dp[i]表示从i这个点开始且i必须选的最大往后找的最大价值,惊奇地发现dp式是长这样的:

dp[i] = dp[j] * k+ a[i]

那么就可以直接转移了

 

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define ll long long
const int MAXN = 1e5 + 3;
double dp[MAXN] , a[MAXN];
int n , m;
int head , tail , que[MAXN];
double k;


int main(){
    scanf( "%d%d" , &n , &m );
    scanf( "%lf" , &k );
    for( int i = 1 ; i <= n ; i ++ ){
        scanf( "%lf" , &a[i] );
    }
    dp[n+1] = 0;
    head = tail = 1, que[head] = n + 1;
    for( int i = n ; i >= 1 ; i -- ){
        while( head < tail && que[head] - m > i ) head ++;
        dp[i] = dp[que[head]] * k + a[i];
        while( head <= tail && dp[que[tail]] <= dp[i] )
            tail --;
        que[++tail] = i;

    }
    for( int i =  1; i <= m; i ++ )
        dp[1] = max( dp[1] , dp[i] );
    printf( "%.2lf" , dp[1] );
    return 0;
}

总结

这道题主要是要想到这里不满足无后效性,所以是二维dp

然后再去想使a[i]的值是一个可以取定的值

所以dp题应该要学会分析dp的性质才行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值