@题目描述@
有 n 名同学要乘坐摆渡车从人大附中前往人民大学,第 i 位同学在第 ti 分钟去等车。只有一辆摆渡车在工作,但摆渡车容量可以视为无限大。摆渡车从人大附中出发、把车上的同学送到人民大学、再回到人大附中(去接其他同学),这样往返一趟总共花费 m 分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。
凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的等车时间之和最小为多少呢?
注意:摆渡车回到人大附中后可以即刻出发。
输入
第一行包含两个正整数 n,m,以一个空格分开,分别代表等车人数和摆渡车往返一趟的时间。
第二行包含 n 个正整数,相邻两数之间以一个空格分隔,第 i 个非负整数 ti代表第 i 个同学到达车站的时刻。
输出
输出一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。
输入样例 1
5 1
3 4 4 3 5
输入样例 2
5 5
11 13 1 5 5
输出样例 1
0
输出样例 2
4
输入输出样例 1 说明
同学 1 和同学 4 在第 3 分钟开始等车,等待 0 分钟,在第 3 分钟乘坐摆渡车出发。摆渡车在第 4 分钟回到人大附中。
同学 2 和同学 3 在第 4 分钟开始等车,等待 0 分钟,在第 4 分钟乘坐摆渡车出发。摆渡车在第 5 分钟回到人大附中。
同学 5 在第 5 分钟开始等车,等待 0 分。
输入输出样例 2 说明
同学 3 在第 1 分钟开始等车,等待 0 分钟,在第 1 分钟乘坐摆渡车出发。摆渡车在第 6 分钟回到人大附中。
同学 4 和同学 5 在第 5 分钟开始等车,等待 1 分钟,在第 6 分钟乘坐摆渡车出发。摆渡车在第 11 分钟回到人大附中。
同学 1 在第 11 分钟开始等车,等待 2 分钟;同学 2 在第 13 分钟开始等车,等待 0 分钟。他/她们在第 13 分钟乘坐摆渡车出发。自此所有同学都被送到人民大学。总等待时间为 4。可以证明,没有总等待时间小于 4 的方案。
数据规模与约定
对于 10% 的数据,n ≤ 10,m = 1,0 ≤ ti ≤ 100。
对于 30% 的数据,n ≤ 20,m≤ 2,0 ≤ ti ≤ 100。
对于 50% 的数据,n ≤ 500,m ≤ 100,0 ≤ ti ≤ 10^4。
另有 20% 的数据,n ≤ 500,m ≤ 10,0 ≤ ti ≤ 4×10^6。
对于 100% 的数据,n ≤ 500,m ≤ 100,0 ≤ ti ≤ 4×10^6。
@绝对不可能是正解的题解@
搜到了不少的题解,一个比一个强 orz。
这个算法应该是一个比较容易“想到”的算法(因为题目中很多的性质都没有用到)
但是有用到斜率优化……
定义
表示第 i 个时刻到达进行等车的人数。
定义
表示在第 i 个时刻发车,前面的同学最小等待时间之和。
则有:
拆开得到:
令
,
。这两货可以前缀和O(n)处理。
则转移方程式变为:
令
。
则
。
典型的不能再典型的斜率优化式…… i 和 h(j) 还具有单调性……
@代码 - 1@
虽然当初我去都没去普及组。
不过普及组(涉嫌)考到斜率优化倒是令我极其感兴趣,就写了一下 qwq。
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef pair<int, ll> pil;
const int INF = (1<<30);
const int MAXN = 4000100;
int cnt[MAXN + 5];
ll sum[MAXN + 5], dp[MAXN + 5];
ll f(int i) {
return cnt[i]*i - sum[i];
}
ll g(int j) {
return dp[j]+sum[j];
}
ll h(int j) {
return cnt[j];
}
double slope(int x, int y) {
if( h(x) == h(y) ) {
if( g(x) > g(y) ) return -INF;
else if( g(x) < g(y) ) return INF;
}
else return 1.0*(g(y) - g(x))/(h(y) - h(x));
}
int que[MAXN + 5];
int main() {
int n, m, x, lim = 0;
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++) {
scanf("%d", &x);
cnt[x]++; lim = max(lim, x);
}
lim += m;
for(int i=1;i<=lim;i++) {
sum[i] = sum[i-1] + 1LL*cnt[i]*i;
cnt[i] += cnt[i-1];
}
int s = 1, t = 0;
que[++t] = 0;
for(int i=1;i<=lim;i++) {
if( i > m ) {
while( s < t && slope(que[t-1], que[t]) >= slope(que[t], i-m) )
t--;
if( h(i-m) != h(que[t]) || g(i-m) != g(que[t]) )
que[++t] = i-m;
}
while( s < t && slope(que[s], que[s+1]) <= i )
s++;
dp[i] = f(i) + g(que[s]) - h(que[s])*i;
}
ll ans = INF;
for(int i=lim-m;i<=lim;i++)
ans = min(ans, dp[i]);
printf("%lld\n", ans);
}
@看起来比较像正解的题解@
考虑到n, m如此之小,我们以它们俩作为状态进行 dp。
首先有这样一个比较关键的性质:车辆停留的时间不会超过 m ,否则它可以往返一次再回来,这样不会影响后面的状态且有可能更优秀。
先按到达时间给人排序。然后定义状态 表示第 i 个人等了 j 分钟,前 i 个人的最小等待时间和。因为上面那个性质,有 j <= 2*m。抵到上界的唯一情况 t[i] = t[i-1],且转移时于 t[i-1] 时刻发车,回来停留 m 分钟。
根据状态定义,可以得到转移方程:
对于(1),第 i 个人与第 i-1 个人同车。
对于(2),第 i 个人与第 i-1 个人不同车。
以上都要满足 0 <= j, k <= 2*m,1<=i<=n。
现在算法时间复杂度为 O(n*m^2) ,虽然已经足以通过此题,但是还可以做到更优秀。
第(1)类转移是O(1)的,不去管它。
第(2)类转移,变换条件得到:k <= t[i]+j-t[i-1]-m。可以用前缀最小值快速求解。
时间复杂度O(n*m)。以及代码复杂度比上面那个斜率优化不知道高到哪里去了。
@代码 - 2@
考场上同学们好像做得很炸……
不过分析下来其实难点也不算多。只是可能状态定义不太好想。
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 500;
const int MAXM = 100;
const int INF = (1<<30);
int t[MAXN + 5];
int dp[MAXN + 5][MAXM*2 + 5];
int mn[MAXN + 5][MAXM*2 + 5];
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
scanf("%d", &t[i]);
sort(t+1, t+n+1);
for(int i=0;i<=2*m;i++) {
dp[1][i] = i;
mn[1][i] = (i == 0) ? dp[1][i] : min(dp[1][i], mn[1][i-1]);
}
for(int i=2;i<=n;i++) {
for(int j=0;j<=2*m;j++) {
dp[i][j] = INF;
if( 0 <= t[i] - t[i-1] + j && t[i] - t[i-1] + j <= 2*m )
dp[i][j] = min(dp[i][j], dp[i-1][t[i]-t[i-1]+j] + j);
if( 0 <= t[i] - t[i-1] + j - m )
dp[i][j] = min(dp[i][j], mn[i-1][min(t[i]-t[i-1]+j-m, 2*m)] + j);
mn[i][j] = (j == 0) ? dp[i][j] : min(dp[i][j], mn[i][j-1]);
}
}
int ans = INF;
for(int i=0;i<=2*m;i++)
ans = min(ans, dp[n][i]);
printf("%d\n", ans);
}
@End@
就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。