题解:[NOIP 2018 普及组] 摆渡车
DP
很明显能想到 DP。
状态设计
不妨令 \(t\) 从小到大有序,设 \(dp_{i,j}\) 表示前 \(i\) 个人在第 \(i\) 个人等待了 \(j\) 时刻时全部上车的最短时间。
状态转移
从其他状态转移到 \(dp_{i,j}\) 不是很好做,因此可以从 \(dp_{i,j}\) 转移到其他状态。
枚举一个 \(k\),表示前 \(i+k\) 个人都上车,且第 \(i+1\sim i+k\) 个人一起上车。
则可以计算得出第 \(i+k\) 个人的等待时间:
\[pl=\max((t_i+j)+m-t_{i+k},0)
\]
和 \(0\) 取 \(\max\) 是因为有可能车已经到了,但是第 \(i+k\) 个人还没有开始等待。
则有:
\[dp_{i+k,pl}\leftarrow\min\left(dp_{i+k,pl},dp_{i,j}+\sum_{l=i+1}^{i+k}(t_{i+k}+pl-t_i)\right)
\]
其中,\(\sum\limits_{l=i+1}^{i+k}(t_{i+k}+pl-t_i)\) 表示第 \(i+1\sim i+k\) 个人的总等车时间。
化简,可以得到:
\[dp_{i+k,pl}\leftarrow\min\left(dp_{i+k,pl},dp_{i,j}+k(t_{i+k}+pl)-\sum_{l=i+1}^{i+k}t_i\right)
\]
很明显,做一个 \(t\) 的前缀和即可单次实现 \(\mathcal O(1)\) 转移,总转移复杂度 \(\mathcal O(n)\)。
AC 代码
时间复杂度:\(\mathcal O\left(n^2m\right)\),状态量 \(\mathcal O(nm)\),转移 \(\mathcal O(n)\)。
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=500,M=100,T=4e6;
int n,m,t[N+1],sum[N+1],dp[N+1][M+1];
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>t[i];
}
sort(t+1,t+n+1);
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+t[i];
}
memset(dp,0x3f,sizeof(dp));
t[0]=-(1<<30);
dp[0][0]=0;
for(int i=0;i<=n;i++){
for(int j=0;j<=min(m-1,t[i+1]-t[i]);j++){
for(int k=1;i+k<=n;k++){
int pl=max(t[i]+j+m-t[i+k],0);
dp[i+k][pl]=min(dp[i+k][pl],dp[i][j]+k*(pl+t[i+k])-(sum[i+k]-sum[i]));
}
}
}
int ans=dp[n][0];
for(int i=1;i<m;i++){
ans=min(ans,dp[n][i]);
}
cout<<ans<<'\n';
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}