有n名同学要乘坐摆渡车从人大附中前往人民大学,第i位同学在第ti分钟去等车。只有一辆摆渡车在工作,但摆渡车容量可以视为无限大。摆渡车从人大附中出发、把车上的同学送到人民大学、再回到人大附中(去接其他同学),这样往返一趟总共花费m分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。
凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的等车时间之和最小为多少呢?
注意:摆渡车回到人大附中后可以即刻出发。
第一行包含两个正整数n,m,以一个空格分开,分别代表等车人数和摆渡车往返一趟的时间。
第二行包含n个正整数,相邻两数之间以一个空格分隔,第i个非负整数ti代表第i个同学到达车站的时刻。
一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。
5 1
3 4 4 3 5
0
5 5
11 13 1 5 5
4
样例一说明:
同学1和同学4在第3分钟开始等车,等待0分钟,在第3分钟乘坐摆渡车出发。摆渡车在第4分钟回到人大附中。
同学2和同学3在第4分钟开始等车,等待0分钟,在第4分钟乘坐摆渡车出发。摆渡车在第5分钟回到人大附中。
同学5在第5分钟开始等车,等待0分钟,在第5分钟乘坐摆渡车出发。自此所有同学都被送到人民大学。总等待时间为 0。
样例二说明:
同学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≤10000。
另有20%的数据,n≤500,im≤10,0≤ti≤1000000。
对于100%的数据,n≤500,m≤100,0≤ti≤4000000。
话说当年考场上做这道题时感觉毫无头绪,考完noip后也一直把这题弃着,直到放假了有时间做,发现也就这么一回事。
洛谷上评分是蓝题,个人感觉顶多绿题。。。
回到正题,观察这一题的数据范围,可以看出$n,m$都很小,那我们可以考虑从这方面进行dp。
首先我们按照升序对$a[i]$进行排序(由于个人习惯,这里这里及下文用$a[i]$表示$t[i]$)。
那我们设$dp[i][j]$为第$i$个同学在$a[i]+j$的时间点出发时,第$1$到第$i$个同学的等待时间之和的最小值,可以想到$0 \leqslant j < m$。(如果$m \leqslant j$的话,那正在等待的同学实际上可以在$a[i]+j-m$的时间点出发,不需要继续等下去,故$0 \leqslant j < m$)
容易想到,$dp[i][j]=min \left \{dp[ii][jj]+wait(i, j, ii) \right \}$($0 \leqslant ii < i$),其中$wait(i, j, ii)$为第$ii$到第$i$个同学的等待时间之和。
根据上文对$dp[i][j]$的定义,可以得到:$a[i] + j < a[i + 1]$同理$a[ii] + jj < a[ii + 1]$。同时,根据这个方程还可以得到$a[ii] + jj + m \leqslant a[i] + j$。
同时我们也可以想到,如果设$s[i] = \sum_{j = 1}^{i} a[j]$,那么$wait(i, j, ii) = (a[i] + j) \times (i - ii) - (s[i] - s[ii])$
此时我们可以得到一个时间复杂度为$O(n^{2}m^{2})$的程序,是可以拿70pts的。
#include <iostream> #include <cstring> #include <algorithm> #define MAX_N (500 + 5) #define MAX_M (100 + 5) #define INF 0x7f7f7f7f #define WAIT(i, j, ii) ((a[(i)] + j) * ((i) - (ii)) - (s[(i)] - s[(ii)])) using namespace std; int n, m; int a[MAX_N], s[MAX_N]; int dp[MAX_N][MAX_M]; int ans = INF; int main() { cin >> n >> m; for(register int i = 1; i <= n; ++i) { cin >> a[i]; } sort(a + 1, a + n + 1); for(register int i = 1; i <= n; ++i) { s[i] = s[i - 1] + a[i]; } memset(dp, 0x7f, sizeof dp); a[0] = a[1] - m; a[n + 1] = INF; for(register int j = 0; j < m; ++j) { dp[0][j] = 0; } int tmp; for(register int i = 1; i <= n; ++i) { for(register int j = 0; j < m && a[i] + j < a[i + 1]; ++j) { for(register int ii = 0; ii < i; ++ii) { tmp = INF; for(register int jj = 0; jj < m && a[ii] + jj < a[ii + 1] && a[ii] + jj + m <= a[i] + j; ++jj) { tmp = min(tmp, dp[ii][jj]); } if(tmp < INF) dp[i][j] = min(dp[i][j], tmp + WAIT(i, j, ii)); } } } for(register int j = 0; j < m; ++j) { ans = min(ans, dp[n][j]); } cout << ans; return 0; }
观察上面的方程,显然最容易优化的就是求“$min \left \{dp[ii][jj]+wait(i, j, ii) \right \}$"的部分,我们可以想一下是否可以开一个数组维护这些最小值。
事实上,这是可行的。
由于这个方程中,始终有$0 \leqslant jj$,那我们何不更改$dp[i][j]$的定义为:第$i$个同学在$\left [ a[i], a[i] + j \right ]$的时间段里出发时,第$1$到第$i$个同学的等待时间之和的最小值$。
这样,我们只需要每次更新完当前的$dp[i][j]$时,和$dp[i][j-1]$取一个最小值就行了,这样我们就可以把时间复杂度降到$O(n^{2}m)$,具体的细节可以参考下面给出的程序。
#include <iostream> #include <cstring> #include <algorithm> #define MAX_N (500 + 5) #define MAX_M (100 + 5) #define INF 0x7f7f7f7f #define WAIT(i, j, ii) ((a[(i)] + j) * ((i) - (ii)) - (s[(i)] - s[(ii)])) using namespace std; int n, m; int a[MAX_N], s[MAX_N]; int dp[MAX_N][MAX_M]; int ans = INF; int main() { cin >> n >> m; for(register int i = 1; i <= n; ++i) { cin >> a[i]; } sort(a + 1, a + n + 1); for(register int i = 1; i <= n; ++i) { s[i] = s[i - 1] + a[i]; } memset(dp, 0x7f, sizeof dp); a[0] = a[1] - m; a[n + 1] = INF; for(register int j = 0; j < m; ++j) { dp[0][j] = 0; } int tmp; for(register int i = 1; i <= n; ++i) { for(register int j = 0; j < m; ++j) { if(a[i] + j < a[i + 1]) { for(register int ii = 0; ii < i; ++ii) { tmp = min(m - 1, min(a[ii + 1] - a[ii] - 1, (a[i] + j) - (a[ii] + m))); if(tmp < 0) continue; if(dp[ii][tmp] < INF) dp[i][j] = min(dp[i][j], dp[ii][tmp] + WAIT(i, j, ii)); } } dp[i][j] = min(dp[i][j], dp[i][j - 1]); } } cout << dp[n][m - 1]; return 0; }
由于我比较菜,有一个$O(nm)$的解法就以后再补充吧。
原文:https://www.cnblogs.com/kcn999/p/10800160.html