题目
题目描述
小奇去遗迹探险,遗迹里有
N
N
N 个宝箱,有的装满了珠宝,有的装着废品。
小奇有地图,所以它知道每一个宝箱的价值,但是它不喜欢走回头路,所以要按顺序拿这 N N N 个宝箱中的若干个。
拿宝箱很累的。一开始小奇的体力是 1 1 1 ,每得到一个宝箱之后,小奇得到的价值是体力 × \times × 宝箱的价值,之后它的体力就会变为原来的 k k k 倍 。
小奇不喜欢连续放过很多宝箱,所以任意一段长度为 M M M 的序列中,小奇一定要取走其中的一个宝箱。
现在小奇想知道它能得到的最大价值和。
输入格式
第一行,两个整数
N
,
M
(
1
≤
M
≤
N
)
N,M(1\le M\le N)
N,M(1≤M≤N) ,表示的含义如题目中所述;
第二行,一个不超过四位的小数 k ( 0 < k < 1 ) k(0<k<1) k(0<k<1) ,表示的含义如题目中所述;
第三行, N N N 个整数,第 j j j 个整数表示第 j j j 个宝箱的价值。
输出格式
输出一行,一个实数,表示小奇能得到的最大价值和,四舍五入保留两位小数。
数据范围与提示
N
≤
1
0
5
N\le 10^5
N≤105 ,宝箱的价值绝对值不超过
1
0
9
10^9
109 。
思路
将第 x x x 个宝箱的价值记为 v x v_x vx 。
暴力
用
f
(
x
,
y
,
z
)
f(x,y,z)
f(x,y,z) 表示前
i
i
i 个点,上一个是
j
j
j ,已经拿了
k
k
k 个。
O
(
n
4
)
\mathcal O(n^4)
O(n4) 。跑出来了估计都退役了。
动态规划
用
f
(
x
,
y
)
f(x,y)
f(x,y) 表示 走到了
x
x
x 点并且把它拿了,一共拿了
y
y
y 个宝箱。那么
f
(
x
,
y
)
=
k
y
−
1
v
x
+
max
u
=
x
−
m
x
−
1
f
(
u
,
y
−
1
)
f(x,y)=k^{y-1}v_x+\max_{u=x-m}^{x-1}f(u,y-1)
f(x,y)=ky−1vx+maxu=x−mx−1f(u,y−1) 。本来是
O
(
n
3
)
\mathcal O(n^3)
O(n3) ,滑窗优化一下就
O
(
n
2
)
\mathcal O(n^2)
O(n2) 了。听说用脚造数据的出题人这个做法放了你90分。
高级动态规划
用 f ( x ) f(x) f(x) 表示 从 x x x 往后走 并且把 x x x 拿了。那么!
f ( x ) = v x + k ⋅ max u = x + 1 x + m f ( u ) f(x)=v_x+k\cdot\max_{u=x+1}^{x+m}f(u) f(x)=vx+k⋅u=x+1maxx+mf(u)
仍然滑窗, O ( n ) \mathcal O(n) O(n) 。奥妙重重!
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <cmath>
#include <queue>
using namespace std;
double Max(const double &a,const double &b){
if(a+1e-6 < b)
return b;
return a;
}
const int MaxN = 100005;
const double infty = 1e100;
int n, m, a[MaxN]; double k;
double dp[MaxN];
deque<int> dq; // 滑窗
void pushInto(int x){
while(not dq.empty()){
int t = dq.back();
if(dp[t]+1e-6 < dp[x])
dq.pop_back();
else break;
}
dq.push_back(x);
}
int main(){
scanf("%d %d %lf",&n,&m,&k);
for(int i=1; i<=n; ++i)
scanf("%d",&a[i]);
for(int i=1; i<=n; ++i)
dp[i] = -infty;
dq.push_back(n+1); // init
for(int i=n; i>=1; --i){
while(dq.front() > i+m)
dq.pop_front();
dp[i] = a[i]+k*dp[dq.front()];
pushInto(i);
}
double ans = -infty;
for(int i=1; i<=m; ++i)
ans = Max(ans,dp[i]);
printf("%.2lf",ans);
return 0;
}