zoukankan      html  css  js  c++  java
  • 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)

      

  • 相关阅读:
    div居中方法总结
    windows下配置nginx环境
    webpack+babel+react操作小结
    JavaScript数组常用操作总结
    MyBatis使用Generator自动生成代码
    如何上Chrome谷歌商店
    深入理解Spring IOC
    SpringMVC概要总结
    mybatis防止sql注入
    Redis和Memcache的区别分析
  • 原文地址:https://www.cnblogs.com/kcn999/p/10800160.html
Copyright © 2011-2022 走看看