【AtCoder Grand Contest 007 D】Shik and Game
单调队列优化 DP。
题目大意:
在数轴上有 $n$ 个关键点,你在点 $0$,终点在点 $E$。保证关键点在 $0\sim E$ 之间。
一个关键点从第一次经过的时刻算起,经过 $T$ 秒后会在那个点处产生一个金币。
你要从 $0$ 走到 $E$,并拿到所有金币。每秒可以向左或向右走一格。问至少需要多少秒。
题解:
令 $f_i$ 表示走到关键点 $i$,并拿到前 $i$ 枚金币的最小时刻。我们走的路径显然分成若干段,每段由:左到右(碰到关键点),右到左(回到左边),左到右(拿金币)组成。
设 $p_i$ 为关键点的位置(令 $p_0=0$),则:
转移的时候我们考虑 $\max$ 里的东西,其中一个临界点为 $T-2p_i$,这是一个和 $i$ 有关的常数。
对于所有的状态 $j$,我们显然存在一个点 $p$ 满足当 $1\leq j\leq p$ 时,$T-2p_i<-2p_j$,而当 $p\lt j\lt i$ 时,$T-2p_i\geq -2p_j$。
对于 $-2p_j$ 作为取值的,显然每次都用最优的那个更新。动态更新之前的最优值即可。
对于 $T-2p_i$ 作为取值的,我们取这个区间里的最优的更新。单调队列优化即可。
时间复杂度 $O(n)$。
Code:
#include<cstdio>
#include<algorithm>
const int N=1e5+5;
typedef long long LL;
int n,E,T,p[N];
LL dp[N];
int q[N],h,t;
int main(){
scanf("%d%d%d",&n,&E,&T);
for(int i=1;i<=n;++i)scanf("%d",p+i),dp[i]=0x7fffffffffffffff;
int pre=0,mpos=0;
for(int i=1;i<=n;++i){
const int TL=T-2*p[i];
while(h<t&&TL<=-2*p[q[h]+1])++h;
int j=q[h];
dp[i]=dp[j]-p[j]+std::max(TL,-2*p[j+1])+3LL*p[i];
while(pre<i&&TL<=-2*p[pre+1]){
if(dp[mpos]-p[mpos]-2LL*p[mpos+1]>dp[pre]-p[pre]-2LL*p[pre+1])
mpos=pre;
++pre;
}
j=mpos;
dp[i]=std::min(dp[i],dp[j]-p[j]+std::max(TL,-2*p[j+1])+3LL*p[i]);
while(h<=t&&dp[q[t]]-p[q[t]]>=dp[i]-p[i])--t;
q[++t]=i;
}
printf("%lld\n",dp[n]+E-p[n]);
return 0;
}