【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 }