AGC007 D Shik and Game

15 篇文章 0 订阅

题意

数轴上有N个地方可以产金币,你首先需要去激活它,然后在T秒后会产出一个金币。每个地方只会产一次。问收集所有N个硬币,并最终走到E的最短时间

题解

考虑dp
定义d[i]表示捡完[1,i]最小花费
转移式
d [ i ] = m i n ( d [ i ] , d [ j ] + a [ i ] − a [ j ] + m a x ( a [ i ] − a [ j + 1 ] , T − ( a [ i ] − a [ j + 1 ] ) ) + m a x ( a [ i ] − a [ j + 1 ] , T − m a x ( a [ i ] − a [ j + 1 ] , T − ( a [ i ] − a [ j + 1 ] ) ) ) ) d[i]=min(d[i],d[j]+a[i]-a[j]+max(a[i]-a[j+1],T-(a[i]-a[j+1]))+max(a[i]-a[j+1],T-max(a[i]-a[j+1],T-(a[i]-a[j+1])))) d[i]=min(d[i],d[j]+a[i]a[j]+max(a[i]a[j+1],T(a[i]a[j+1]))+max(a[i]a[j+1],Tmax(a[i]a[j+1],T(a[i]a[j+1]))))
好像写得比较复杂
简化一下就是
d [ i ] = d [ j ] + a [ i ] − a [ j ] + m a x ( T , 2 ∗ ( a [ i ] − a [ j + 1 ] ) ) d[i]=d[j]+a[i]-a[j]+max(T,2*(a[i]-a[j+1])) d[i]=d[j]+a[i]a[j]+max(T,2(a[i]a[j+1]))
所以 O ( N 2 ) O(N^2) O(N2)的算法就完成了
那么怎么优化呢
其实主要是那个max的问题,然后又考虑到a[i]-a[j+1]是随着i的增加一直增加的,我们可以用一个单调队列来维护,我们维护一个单增的队列,然后维护队首2*(a[i]-a[j+1])<=T,然后从队首转移,这时候转移式就变成了 d [ i ] = d [ j ] + a [ i ] − a [ j ] + T d[i]=d[j]+a[i]-a[j]+T d[i]=d[j]+a[i]a[j]+T
而那些已经出队的点呢?这时候转移式就变成了 d [ i ] = d [ j ] + a [ i ] − a [ j ] + 2 ∗ ( a [ i ] − a [ j + 1 ] ) d[i]=d[j]+a[i]-a[j]+2*(a[i]-a[j+1]) d[i]=d[j]+a[i]a[j]+2(a[i]a[j+1])
或者再化简一下 d [ i ] = 3 ∗ a [ i ] + d [ j ] − a [ j ] − 2 ∗ a [ j + 1 ] ) d[i]=3*a[i]+d[j]-a[j]-2*a[j+1]) d[i]=3a[i]+d[j]a[j]2a[j+1])
所以对于所有出队的节点,我们储存一个最小的 d [ j ] − a [ j ] − 2 ∗ a [ j + 1 ] d[j]-a[j]-2*a[j+1] d[j]a[j]2a[j+1],然后转移即可
最后的d[n]并不是最终答案,我们还要加上m-a[n]
考场上为了保险写的分段…

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100005;
typedef long long ll;
const ll INF=1e15;
ll d[N];
int n,m,T;
ll a[N];
int q[N];
int s,t;
int main()
{
    //freopen("computer.in","r",stdin);
    //freopen("computer.out","w",stdout);
    scanf("%d%d%d",&n,&m,&T);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        d[i]=INF;
    }
    if(n<=1000){
        for(int i=1;i<=n;i++)
            for(int j=0;j<i;j++){
                ll f=max(a[i]-a[j+1],T-(a[i]-a[j+1]));
                d[i]=min(d[i],d[j]+a[i]-a[j]+f+max(a[i]-a[j+1],T-f));
            }
        ll ans=d[n]+m-a[n];
        printf("%lld\n",ans);
    }
    else{
        s=1,t=1;
        ll z=INF;
        for(int i=1;i<=n;i++){
            while(s<=t&&2*(a[i]-a[q[s]+1])>T){
                z=min(z,d[q[s]]-a[q[s]]-2*a[q[s]+1]);
                s++;
            }
            d[i]=min(d[i],a[i]+z+2*a[i]);
            //d[i]=min(d[i],d[q[s]]+a[i]-a[q[s]]+2*(a[i]-a[q[s]+1]));
            d[i]=min(d[i],d[q[s]]+a[i]-a[q[s]]+T);
            while(s<=t&&d[q[s]]-a[q[s]]>=d[i]-a[i])
                t--;
            q[++t]=i;
        }
        ll ans=d[n]+m-a[n];
        printf("%lld\n",ans);
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值