擺渡車
定義,無比重要
顯然的,我們有一個dp的想法,類似於遞降子序列的想法
我們顯然的,這是一個一維dp,所以,我們定義\(f[i]\)函數,為\(0\)\(-i\)的時間內,所出現顧客的等待時間的最小值
但是,有問題,這樣的狀態,沒有dp轉移方程。。。。。因為,狀態描述不清,你有一個發車間隔為\(m\)
所以我們吧\(dp\)的狀態強化,為\(dp[i]\),表示在\(i\)時刻發車\(0-i\)時出現的乘客等待時間的的最小值
這樣我們就把\(dp\)的狀態描述清楚了我們再來推\(dp\)的轉移方程
我們照着這個直接抄就好了
需要注意的是,我們在這里要做一個前綴和,讓轉移的復雜度降下來,但那也是\(O(n^2)\)的會超時
不妨,我們即令\(cnt_i\),\(0-i\)中出現的人數,而\(sum_i\),為其出現時間\(t_k\)之和
\(50pts\)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <bits/stdc++.h>
using namespace std;
const int MaxT=4000011;
int n,m,ti,t,ans=0x7fffffff,cnt[MaxT],sum[MaxT],f[MaxT];
int main() {
n=read();
m=read();
for(int i=1;i<=n;i++){
ti=read();
t=max(ti,t);
cnt[ti]++;
sum[ti]+=ti;
}
for(int i=1;i<t+m;i++){
cnt[i]+=cnt[i-1];
sum[i]+=sum[i-1];
}
for(int i=0;i<t+m;i++){
f[i]=cnt[i]*i-sum[i];
for(int j=0;j<=i-m;j++){
f[i]=min(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j]));
}
}
for(int i=t;i<t+m;i++){
ans=min(ans,f[i]);
}
printf("%d",ans);
return 0;
}
這能得到50ps的高分
\(70pts\)
我們再來想想優化,事實上我們枚舉j時不需令\(j\)從0開始,我們減去無用的轉移從
事實上,從貪心的角度出發,在這一段\(>2m\)的長度里,發一次車,一定比不發車要好,所以
我們令\(i-2m<j\le i-m\)
這樣根據寫法,可以獲得\(70\)至\(75\),分數
時間復雜度為\(O(tm)\)
\(100pts\)
我們其實稍微看看,聯想一下過河那個題,考慮到路徑壓縮,因為讓人很稀疏,\(t\)很大,事實上無論如果你是循環枚舉決策,只能是\(O(tm)\);
然而在這個題里,考慮路徑壓縮的一個思想,但是那個題模1000,真的是無語了,解法不漂亮,在這個題里,數組是夠的,然而你不能模一個東西,因為與\(m\)有關
在\([i,i+m)\),里如果沒有讓人,那我們把這一段往右移令,\(f_i\)=\(f_{i-m}\),j時間復雜度為\(O(n^2m+t)\)
至於為什么有右移,我們的\(dp\),定義為在第\(i\)時間發車的等待時間最小值,而\([i,i+m)\)已經沒有人了,在做\(i+m\)時上一輛的回來時間至近為\(i\),若不是
我們先證明一個引理:
- 任何顧客的等待時間不超過m(小於)
由此
得出我們剪枝的正確性。。。。
其實我也不確定。。但是對了?
#include <iostream>
#include <cstdio>
#include <cstring>
#include <bits/stdc++.h>
using namespace std;
const int MaxT=4000011;
int n,m,ti,t,ans=0x7fffffff,cnt[MaxT],sum[MaxT],f[MaxT];
int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch-'0');
ch=getchar();
}
return x;
}
int main() {
// freopen("1.in","r",stdin);
n=read();
m=read();
for(int i=1;i<=n;i++){
ti=read();
t=max(ti,t);
cnt[ti]++;
sum[ti]+=ti;
}
for(int i=1;i<t+m;i++){
cnt[i]+=cnt[i-1];
sum[i]+=sum[i-1];
}
for(int i=0;i<t+m;i++){
if(i>=m&&cnt[i-m]==cnt[i]){
f[i]=f[i-m];
continue;
}
f[i]=cnt[i]*i-sum[i];
for(int j=max(i-2*m+1,0);j<=i-m;j++){
f[i]=min(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j]));
}
}
for(int i=t;i<t+m;i++){
ans=min(ans,f[i]);
}
printf("%d",ans);
return 0;
}
但是,我們知道,這樣子一點也沒有\(bg\),對吧,要優美、
考慮,之前做過的玩具裝箱,不是一樣的嗎?
所以我們考慮斜率優化,,,
接下來是數學的操作
手稿
下面時正文推導
首先,為了書寫方便令\(j\)為最優決策點
我們把dp方程做變形有
於是等號左邊為\(y\),\(i\)為\(k\),\(cnt_j\)為\(x\),\(({f_i+sum_i-cnt_i*i} )\)為\(b\),我們要求\(b\)
配個小圖
不妨建立一個平面直角坐標系令每個點坐標為\((cnt_i,f_j+sum_j)\),這就很明顯了,過這個點做一條斜率為\(i\)的直線,求截距最小值,再兩個點斜率小於i時隊首++,然后i點入隊(提前要把一些不是凸包的點初隊)
我們通過維護一個雙端隊列,來完成這個操作。
程序寫的時候注意細節
#include <iostream>
#include <cstdio>
#include <cstring>
#include <bits/stdc++.h>
using namespace std;
const int MaxT=4000011;
int n,m,ti,t,ans=0x7fffffff,tail,head,cnt[MaxT],sum[MaxT],f[MaxT],que[MaxT];
double slope(int i,int j){
if(cnt[i]==cnt[j]) return double(f[j]+sum[j]-f[i]-sum[i])/1e-9;
else return double(f[j]+sum[j]-f[i]-sum[i])/double(cnt[j]-cnt[i]);
}
int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch-'0');
ch=getchar();
}
return x;
}
int main() {
// freopen("1.in","r",stdin);
n=read();
m=read();
for(int i=1;i<=n;i++){
ti=read();
t=max(ti,t);
cnt[ti]++;
sum[ti]+=ti;
}
for(int i=1;i<t+m;i++){
cnt[i]+=cnt[i-1];
sum[i]+=sum[i-1];
}
/*
for(int i=0;i<t+m;i++){
if(i>=m&&cnt[i-m]==cnt[i]){
f[i]=f[i-m];
continue;
}
f[i]=cnt[i]*i-sum[i];
for(int j=max(i-2*m,0);j<=i-m;j++){
f[i]=min(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j]));
}
}
*/
head=1;tail=0;
for(int i=0;i<t+m;i++){
if(i<m){
f[i]=cnt[i]*i-sum[i];
continue;
}
while(head<tail&&slope(que[tail-1],que[tail])>=slope(que[tail-1],i-m))
tail--;
tail++;
que[tail]=i-m;
while(head<tail&&slope(que[head],que[head+1])<=i) head++;
f[i]=f[que[head]]+(cnt[i]-cnt[que[head]])*i-(sum[i]-sum[que[head]]);
}/*
for(int i=0;i<t+m;i++){
printf("%d %d\n",i,f[i]);
}*/
for(int i=t;i<t+m;i++){
ans=min(ans,f[i]);
}
printf("%d",ans);
return 0;
}
時間復雜度\(O(t)\),我覺得很優秀了,不是嗎。