1. 程式人生 > >Luogu 5017 NOIP2018普及組T3 擺渡車 (斜率優化 + 必要的轉移進行剪枝)

Luogu 5017 NOIP2018普及組T3 擺渡車 (斜率優化 + 必要的轉移進行剪枝)

題意:

  有 名同學要乘坐擺渡車從人大附中前往人民大學,第 位同學在第 ti 分鐘去 等車。只有一輛擺渡車在工作,但擺渡車容量可以視為無限大。擺渡車從人大附中出發、 把車上的同學送到人民大學、再回到人大附中(去接其他同學),這樣往返一趟總共花費 m 分鐘(同學上下車時間忽略不計)。擺渡車要將所有同學都送到人民大學。

  凱凱很好奇,如果他能任意安排擺渡車出發的時間,那麼這些同學的等車時間之和最小為多少呢?

  本人感覺自己語文不是那麼的好,無法概括。

細節:

  咳咳,此題細節都在題意之中嘻嘻嘻,原來懶還有這個好處。

分析:

  round1:從時間入手狀態迎刃而解:f[i] 表示以 i 作為發車時間的最小等車時間之和。

  則轉移就為:f[i] = min{ f[j] + Σj<tk≤i i-t}

  但是這個轉移不免需要迴圈計算求和的內容,我們需要考慮將其利用 O(1) 進行計算完畢,考慮將這個式子進行開啟:

    Σj<tk≤i i-tk = i×Σj<tk≤i 1 - Σj<tk≤i tk,不難發現此時我們只需要預處理出 t出現位置的字首陣列 cnt[i],以及 tk 出現位置的值的字首和 sum[i]。

  這樣方程進行進一步轉化:f[i] = min{ f[j] + ( cnt[i] - cnt[j] ) × i - ( sum[i] - sum[j] ) }

  最後觀察其是否存在單調性,顯然上方的 1D/1D 的動態規劃需要進行斜率優化了,裸體希望有這些意識吧,常規套路,最後只需要維護一個斜率:

    k = ( f[j2] + sum[j2] - f[j1] - sum[j1] ) / (cnt[j2] - cnt[j1]) 單調遞增即可,具體方法詳見本人的第一篇部落格。 

 

  round2:確實從時間入手是一種思路,但是對於較小的 n 和 m 很多選手應該也會有想法,不難發現對於第 i 個人對應的等待時間 t

它的上車時間是有一定的範圍的,它就是[ ti , ti + m),因為當超出這個範圍時要麼是等車的人還沒來,要麼等車的人能乘坐上一班車子前往目的地,所以我們可以考慮一個狀態:f[i][j] 表示第 i 個人乘車切在 ti + j 的時間中上了車的最小等車時間,其狀態必須保證 i + 1 這個人無法上車,且 i 之前可能有人跟 i 一起在車上。

  不難發現對於 i 之後的一個位置 k 其中這段區間 [i+1 , k] 的人上車時間可以進行計算,tmp 表示這段人的上車時間 = t[i] + j + m - t[i+k],所以我們出現了最原始的轉移:

    f[k][tmp] = f[i][j] + Σi+1<=x<=k(tmp + t[k] - t[x])

  仍然需要把上方的轉移優化成 O(1),使用一個裸的字首和就可以優化掉了:

    f[k][tmpf[i][jtmt[k× − − ( sumt[k− sumt[i)

  最後這個方程仍然可以進行優化,通過把 "我為人人" -> "人人為我" 就可以再一次進行斜率優化時間複雜度就可以變為 O(n),本人比較懶開始拒絕思考了。

程式碼:

Round1:

#include<bits/stdc++.h>
#define MAXT 4000505
using namespace std;

int n, m, cnt[MAXT], sum[MAXT], f[MAXT], que[MAXT], Maxt;

int main(){
    scanf("%d%d", &n, &m);
    for (int i=1; i<=n; i++){
        int x;
        scanf("%d", &x);
        cnt[x]++, sum[x]+=x;
        Maxt=max(Maxt, x);
    }
    for (int i=1; i<Maxt+m; i++) cnt[i]+=cnt[i-1], sum[i]+=sum[i-1];
    int head=1, tail=0;
    for (int i=1; i<Maxt+m; i++){
        if (i>=m) {
            while (head<tail && (f[que[tail]]+sum[que[tail]]-f[que[tail-1]]-sum[que[tail-1]])*(cnt[i-m]-cnt[que[tail]])>=(f[i-m]+sum[i-m]-f[que[tail]]-sum[que[tail]])*(cnt[que[tail]]-cnt[que[tail-1]])) --tail;
            que[++tail]=i-m;
        }
        while (head<tail && i*(cnt[que[head+1]]-cnt[que[head]])>=(f[que[head+1]]+sum[que[head+1]]-f[que[head]]-sum[que[head]])) ++head;
        f[i]=cnt[i]*i-sum[i];
        if (head<=tail) f[i]=min(f[i], f[que[head]]+(cnt[i]-cnt[que[head]])*i-(sum[i]-sum[que[head]]));
    }
    int ans=1000000000;
    for (int i=Maxt; i<Maxt+m; i++) ans=min(ans, f[i]);
    printf("%d\n", ans);
    return 0;
} 
Round2:

#include<bits/stdc++.h>
using namespace std;
int t[501],s[501],f[501][101];
const int inf=2139062143;
inline int read()
{
    int neg=1,x=0;
    char c;
    while((c=getchar())<'0'||c>'9')
        if(c=='-')
            neg=-1;
    x=c-'0';
    while((c=getchar())>='0'&&c<='9')
        x=x*10+(c-'0');
    return x*neg;
}
int main()
{
    int n=read(),m=read();
    for(int i=1;i<=n;i++)
        t[i]=read();
    sort(t+1,t+n+1);
    for(int i=1;i<=n;i++)
        s[i]=s[i-1]+t[i];
    memset(f,0x7f,sizeof(f));
    t[0]=-inf;
    f[0][0]=0;
    for(int i=0;i<=n;i++)
    {
        int MAX=min(m-1,t[i+1]-t[i]);
        for(int j=0;j<=MAX;j++)
            if(f[i][j]!=inf)
                for(int k=1;i+k<=n;k++)
                {
                    int tmp=max(t[i]+j+m-t[i+k],0);
                    f[i+k][tmp]=min(f[i+k][tmp],f[i][j]+(tmp+t[i+k])*k-(s[i+k]-s[i]));
                }
    }
    int ans=inf;
    for(int i=0;i<m;i++)
        ans=min(ans,f[n][i]);
    printf("%d\n",ans);
    return 0;
}