首先扯一句题外话:B 出题人 ****。nmd 老子开学前一天打你午夜的比赛,还出个结论题糟蹋我比赛体验?还好把这个 C 做出来了,要不然玩完了。
洛谷题目页面传送门 & CF 题目页面传送门
题意见洛谷。
注意到一个 boss 的血量为 (2),于是每一级最多会被强制离开 (1) 次。
于是考虑对于每一级决定是否被强制离开。设若不离开,那么第 (i) 级最少需要 (x_i) 的时间打怪;若离开,那么第 (i) 级最少需要 (y_i) 的时间打怪(这个 (x_i,y_i) 太好算了,显然可以 (mathrm O(1)),就不再赘述了)。显然,对于一种决定,总时间就是它们的打怪时间相加(虽然被强制离开的级别的打怪过程不是连续的,但也可以相加)加上走路的最小总时间。不妨来研究对于任意一个决定,走路的最优路线长什么样子。
若没有被强制离开的级别的话,最优路线显然是从 (1) 笔直走到 (n)。如果有的话,那么那些被强制离开的点要经过 (2) 次(这是显然的吧)。按原笔直路线经过它们的时候,它们只被经过了 (1) 次;要想来个第 (2) 次,肯定要回头;然后还要继续往前走啊,于是回头走到某处的时候还要再回头按原笔直路线走。于是很容易得出结论:最优路线一定是在笔直路线的基础上,有若干个不相交的区间多走了 (2) 遍,其中这些区间要覆盖所有的被强制离开的点。
细节:
- 每个区间的大小要 (>1),因为 (=1) 的话就是原地打转,是不算经过多遍的;
- 若 (n) 处不被强制离开的话,那么右端点为 (n) 的区间是只需要再多走 (1) 遍的,因为到达了左端点之后就没必要再回头了,游戏已经 win 了。
接下来就很容易设计出无后效性的 DP 了(在得出结论之前,瓶颈在于可以往两边跑,后效性消除不了)。(dp_i) 表示考虑到第 (i) 级,停在第 (i) 级且前 (i) 级都打掉了时的最小打怪、额外走路总时间。边界:(dp_0=0,dp_1=x_1);目标:(dp_n+(n-1)d)。转移的话,考虑第 (i) 级是离开还是不离开两种选择:不离开简单要死;离开的话,显然 (i) 是最后一个重复走区间的右端点,于是枚举左端点。所以状态转移方程:
直接算是 (mathrm O!left(n^3 ight)) 的。里层 (sum) 可以前缀和优化掉,剩下来简单拆一下 (min),发现就是个前缀 (min),稍微 two-pointers 简单维护一下就优化成了 (mathrm O(n))。注意,(i=n) 要特殊算,随便跑一下即可,不影响复杂度。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf=0x3f3f3f3f3f3f3f3f;
const int N=1000000;
int n;
int r1,r2,r3,d;
int a[N+1];
int dp[N+1];
int x[N+1],y[N+1];
int Sum[N+1];
signed main(){
cin>>n>>r1>>r2>>r3>>d;
for(int i=1;i<=n;i++)scanf("%lld",a+i);
for(int i=1;i<=n;i++){
x[i]=a[i]*min(r1,r3)+r3;
y[i]=min(r2+min(min(r1,r2),r3),a[i]*min(r1,r3)+min(r1,r2)+min(min(r1,r2),r3));
Sum[i]=Sum[i-1]+min(x[i],y[i]);
// cout<<x[i]<<" "<<y[i]<<"
";
}
int mn=inf;
dp[1]=x[1];
for(int i=2;i<n;i++){
mn=min(mn,dp[i-2]-Sum[i-2]-2*(i-1)*d);
dp[i]=min(dp[i-1]+x[i],mn+Sum[i]+2*i*d);
// cout<<dp[i]<<"
";
}
dp[n]=dp[n-1]+x[n];
for(int i=0;i<=n-2;i++)
dp[n]=min(dp[n],dp[i]+Sum[n]-Sum[i]+2*n*d-2*(i+1)*d),
dp[n]=min(dp[n],dp[i]+Sum[n-1]-Sum[i]+x[n]+n*d-(i+1)*d);
cout<<dp[n]+(n-1)*d;
return 0;
}