链接
题解
现在的普及组题质量还蛮高的
首先这题读完就想到是 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<i−2∗m时转移是重复的,因为这两次发车之间空出了完整的 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;
}