GHOJ 779 摆渡车

题目描述

  有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]=underset{0 leqslant ii < i}{min } {dp[ii][jj]+wait(i, j, ii) }$$

        (其中$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) imes (i - ii) - (s[i] - s[ii])$$

  此时我们可以得到一个时间复杂度为$O(n^{2}m^{2})$的程序。

#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;
} 
参考程序O(n^2 * m^2) 

  观察上面的方程,显然最容易优化的就是求“$min  {dp[ii][jj]+wait(i, j, ii)  }$"的部分,我们可以想一下是否可以开一个数组维护这些最小值。

  事实上,这是可行的。

  由于这个方程中,始终有$0 leqslant jj$,那我们何不更改$dp[i][j]$的定义为:第$i$个同学在$left [ a[i], a[i] + j ight ]$的时间段里出发时,第$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(n^2 * m) 

  实际上这样做还是麻烦了。

  我们可以想,第$i$个人要么和第$i - 1$个人同车,要么和第$i - 1$个人不同车,要在其中取最优解。也就是说,第$i$个人只和第$i - 1$个人有关,也就是说$ii$其实只需要取$i - 1$。

  让$dp[i][j]$回到原来的定义。容易得到,当第$i$个人和第$i - 1$个人同车时,有状态转移方程:

  $$dp[i][j] = dp[i - 1][a[i] + j - a[i - 1]] + j$$

  而当第$i$个人和第$i - 1$个人不同车时,容易得到:

  $$dp[i][j] = underset {0 leqslant k leqslant a[i] + j - a[j] - m, 0 leqslant k < m imes 2} {min} { dp[i - 1][k] } + j$$

  像前面一样维护最小值即可。

#include <iostream>
#include <algorithm>
#include <cstring>

#define MAX_N (500 + 5)
#define MAX_M (100 + 5)

using namespace std;

int n, m;
int a[MAX_N];
int dp[MAX_N][MAX_M << 1], mdp[MAX_N][MAX_M << 1];

int main()
{
    cin >> n >> m;
    for(register int i = 1; i <= n; ++i)
    {
        cin >> a[i];
    }
    sort(a + 1, a + n + 1);
    memset(dp, 0x7f, sizeof dp);
    memset(mdp, 0x7f, sizeof mdp);
    for(register int i = 0; i < (m << 1); ++i)
    {
        dp[1][i] = i;
        mdp[1][i] = 0;
    }
    for(register int i = 2; i <= n; ++i)
    {
        for(register int j = 0, k1 = a[i] - a[i - 1] - m, k2 = a[i] - a[i - 1]; j < (m << 1); ++j, ++k1, ++k2)
        {
            if(k1 >= 0) dp[i][j] = min(dp[i][j], mdp[i - 1][min(k1, (m << 1) - 1)] + j);
            if(k2 >= 0 && k2 < (m << 1)) dp[i][j] = min(dp[i][j], dp[i - 1][k2] + j);
        }
        mdp[i][0] = dp[i][0];
        for(register int j = 1; j < (m << 1); ++j)
        {
            mdp[i][j] = min(dp[i][j], mdp[i][j - 1]);
        }
    }
    cout << mdp[n][(m << 1) - 1];    
    return 0;
}
参考程序O(nm)

  

原文地址:https://www.cnblogs.com/kcn999/p/10800160.html