luoguP5017 摆渡车

链接

点击跳转

题解

现在的普及组题质量还蛮高的

首先这题读完就想到是 d p dp dp

f i f_i fi表示在 i i i时刻发车,这之前出现的人等待的最小总时长是多少

转移就是 f i = m i n ( f j + t ) f_i = min(f_j+t) fi=min(fj+t) t t t就是从 j + 1 j+1 j+1 i i i这个时刻出现的人等待的总时长

然后发现当 j < i − 2 ∗ m j<i-2*m j<i2m时转移是重复的,因为这两次发车之间空出了完整的 m m m时间,我为啥不补发一辆呢?

这样就有了一个 O ( m × m a x ( t i ) ) O(m\times max(t_i)) O(m×max(ti))的做法

然后注意到 n n n很小,那也就是说人出现的很稀疏

这个优化不太好想,注意一个问题,我的 d p dp dp方程中 t t t是两次发车之间的人等待的时长,那么如果两次发车之间没有人, t t t显然等于 0 0 0,那么问题就迎刃而解了,我按时间从小到大 d p dp dp,记录上一次出现的人在哪个时间,如果当前时间和那个时间的差大于 2 m 2m 2m,那么我的 d p dp dp就变成了单纯的区间求最值,一个单调队列就搞定了

最终的复杂度 O ( n m + m a x ( t i ) ) O(nm+max(t_i)) O(nm+max(ti))

代码

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define iinf 0x3f3f3f3f
#define linf (1ll<<60)
#define eps 1e-8
#define maxn 4000010
#define cl(x) memset(x,0,sizeof(x))
#define rep(_,__) for(_=1;_<=(__);_++)
#define em(x) emplace(x)
#define emb(x) emplace_back(x)
#define emf(x) emplace_front(x)
#define fi first
#define se second
#define de(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
ll read(ll x=0)
{
    ll c, f(1);
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
    for(;isdigit(c);c=getchar())x=x*10+c-0x30;
    return f*x;
}
int n, f[maxn], m, cnt[maxn], s[maxn];
int main()
{
    int i, j, t, last=-1;
    n=read(), m=read();
    rep(i,n)cnt[read()]++;
    rep(i,int(4e6))s[i]=s[i-1]+cnt[i];
    deque<int> q;
    for(i=0;i<m;i++)
    {
        if(cnt[i])last=i;
        for(j=0;j<=i;j++)f[i]+=cnt[j]*(i-j);
    }
    for(i=m;i<=(int)4e6;i++)
    {
        while(q.size() and f[q.back()]>f[i-m])q.pop_back(); q.push_back(i-m);
        if(cnt[i])last=i;
        if(i-last>=2*m and last>0)
        {
            while(i-q.front()>=2*m)q.pop_front();
            f[i]=f[q.front()];
            continue;
        }
        f[i]=0x7fffffff;
        t=0;
        for(j=i-1;j>=0 and i-j<2*m;j--)
        {
            if(i-j>=m)f[i]=min(f[i],f[j]+t);
            t+=cnt[j]*(i-j);
        }
    }
    int ans=0x7fffffff;
    for(i=last;i<=int(4e6);i++)ans=min(ans,f[i]);
    cout<<ans;
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值