1. 程式人生 > >【題解】擺渡車

【題解】擺渡車

所有 參考 display spa str 如果 mem include fin

題目描述

  有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;
} 
參考程序(70pts)

  觀察上面的方程,顯然最容易優化的就是求“$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)$的解法就以後再補充吧。

【題解】擺渡車