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

【NOIP2018】擺渡車

 

 

 【題意】

1、第i個同學在第t[i]分鐘到達車站

2、擺渡車一次可以裝下無數人

3、兩次發車的間隔時間m分鐘

求所有等車時間和的最小值

【解題】

我們不妨認為時間是一條數軸,每名同學按照到達時刻分別對應數軸上可能重合的點。

安排車輛的工作,等同於將數軸分成若干個左開右閉段,每段的長度⩾m。原本的等車時間之和,自然就轉換成所有點到各自所屬段右邊界的距離之和。

.

令f(i)表示在第i分鐘發出一班車時,所需要等待的最小時間。最後一個人到車站的時間為t

則有: f(i)=min{f(k)+∑(i-a[j],k<a[j]<=i)} 其中 0<=k<=i-m

其最終答案:ans=min{f(i)} 其中t<=i<t+m

這個DP方程的複雜度為O(t2n)

1、字首和優化

對於∑(i-a[j],k<a[j]<=i)

令cnt[i]表示從0到i時間為止到達車站的人數和

令sum[i]表示從0到i時間為止到達車站的人的時間總和

則有∑(i-a[j],k<a[j]<=i) =i*(cnt[i]-cnt[k])-(sum[i]-sum[k])

即DP方程為 f(i)=min{f(k)+i*(cnt[i]-cnt[k]) -(sum[i]-sum[k])} 其中k<=i-m

此時,時間複雜度降為O(t2

)

2、轉移優化

試想一下,沒有一個同學會等待超過2m分鐘。

因為最壞情況下,在k時刻發出了一輛車,有位同學在k+1時刻到達了車站,擺渡車將會在k+m時刻返回,考慮到等待其他學生的情況,擺渡車最晚會在k+2m-1時刻發出(不然還不如在k+m時刻和k+2m時刻各發出一輛)

此時,DP方程為: f(i)=min{f(k)+i*(cnt[i]-cnt[k]) -(sum[i]-sum[k])} 其中i-2m<k<=i-m

此時,時間複雜度為O(tm)

3、狀態壓縮

很顯然,根據上一條優化的結論,當順序相鄰的兩位同學的時間間隔超過2m的時候,其中的狀態都是無用的,可以直接壓縮至2m

即對a排序後,若a[i+1]-a[i]>2m, 則對後續所有的a[k,k>i]-=a[i+1]-a[i]-2m

另外,可以將DP的起點定為第一個同學到達的時間 則最後到車站的時間t最大為2m*n

此時,時間複雜度為O(tm)=O(m2n) 解決

【程式碼】

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 500 +5;
 4 const int M = 100 +5;
 5 const int T = 4000000 +5;
 6 typedef long long ll;
 7 ll n,m,t;
 8 ll a[N];
 9 ll cnt[T],sum[T],f[T]={0};
10 int main()
11 {
12     scanf("%lld%lld",&n,&m);
13     for (int i=1;i<=n;i++)
14         scanf("%lld",&a[i]);
15     sort(a+1,a+n+1);
16     // 狀態壓縮 
17     ll dd=a[1];
18     for (int i=1;i<=n;i++) 
19     {
20         a[i]-=dd;
21         if (a[i]-a[i-1]>2*m)
22         {
23             dd+=a[i]-a[i-1]-2*m;
24             a[i]=a[i-1]+2*m;
25         }
26         cnt[a[i]]++;
27         sum[a[i]]+=a[i];
28     }
29     t=a[n];
30     // 求字首和 
31     for (int i=1;i<=t+m;i++)
32         cnt[i]+=cnt[i-1],sum[i]+=sum[i-1];
33     // 邊界條件 
34     for (int i=0;i<m;i++)
35         f[i]=i*cnt[i]-sum[i];
36     // DP
37     for (int i=m;i<=t+m;i++)
38     {
39         f[i]=1ll<<60;
40         for (int k=max(0ll,i-2*m);k<=i-m;k++)
41             f[i]=min(f[i],f[k]+i*(cnt[i]-cnt[k])-(sum[i]-sum[k]));
42     }
43     // 求ans 
44     ll ans=1ll<<62;
45     for (int i=t;i<=t+m;i++)
46         ans=min(ans,f[i]); 
47     printf("%lld",ans);
48 }
bus

【進一步優化】

4*、斜率優化

對於:決策 f(i)=f(k)+i*(cnt[i]-cnt[k])-(sum[i]-sum[k])其中k<=i-m

和:決策 f(i)=f(j)+i*(cnt[i]-cnt[j])-(sum[i]-sum[j]) 其中k<j<=i-m

如果決策j優於決策k

即f(k)+i*(cnt[i]-cnt[k])-(sum[i]-sum[k]) > f(j)+i*(cnt[i]-cnt[j])-(sum[i]-sum[j])

化簡得到:

 

令 yk=f(k)+sum[k] xk=cnt[k]

     yj=f(j)+sum[j]   xj=cnt[j]

說明決策j優於決策k

當 k<j<i 時,若g(k,j)>g(j,i),則決策j永遠不可能為最優解

所以有效點集應該呈現下凸性質 從左往右,有效點集的斜率是單調遞增的 可以用單調佇列來維護

此時,時間複雜度為O(nm)

【程式碼】

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 500 +5;
 4 const int M = 100 +5;
 5 const int T = 4000000 +5;
 6 typedef long long ll;
 7 ll n,m,t;
 8 ll a[N];
 9 ll cnt[T],sum[T],f[T]={0};
10 ll l=1,r,q[T];
11 inline double getSlope(int u, int v) { return (double) (f[v] + sum[v] - f[u] - sum[u]) / (cnt[u] == cnt[v] ? 1e-9 : cnt[v] - cnt[u]); }
12 int main()
13 {
14     scanf("%lld%lld",&n,&m);
15     for (int i=1;i<=n;i++)
16         scanf("%lld",&a[i]);
17     sort(a+1,a+n+1);
18     // 狀態壓縮 
19     ll dd=a[1];
20     for (int i=1;i<=n;i++) 
21     {
22         a[i]-=dd;
23         if (a[i]-a[i-1]>2*m)
24         {
25             dd+=a[i]-a[i-1]-2*m;
26             a[i]=a[i-1]+2*m;
27         }
28         cnt[a[i]]++;
29         sum[a[i]]+=a[i];
30     }
31     t=a[n];
32     // 求字首和 
33     for (int i=1;i<=t+m;i++)
34         cnt[i]+=cnt[i-1],sum[i]+=sum[i-1];
35     // DP
36     l=1;r=0;
37     for (int i=0;i<=t+m;i++)
38     {
39         if (i-m>=0)
40         {
41             while (l<r&&getSlope(q[r-1],q[r])>=getSlope(q[r],i-m)) r--;
42             q[++r]=i-m;
43         }
44         while (l<r&&getSlope(q[l],q[l+1])<=i) l++;
45         f[i]=cnt[i]*i-sum[i];  // 邊界情況
46         if (l<=r) f[i]=min(f[i],f[q[l]]+(cnt[i]-cnt[q[l]])*i-(sum[i]-sum[q[l]])); 
47     }
48     // 求ans 
49     ll ans=1ll<<62;
50     for (int i=t;i<=t+m;i++)
51         ans=min(ans,f[i]); 
52     printf("%lld",ans);
53 }