NOIP2018 普及組 擺渡車 題解

2022-05-07 03:21:12 字數 2939 閱讀 5372

《關於我上冬令營網課時,聽說普及組考過斜率優化dp這件事》擺渡車往返一次要 \(m\) 分鐘,但擺渡車可以在起點等人,故可以將往返一次的時間 \(t \in [m,\infty )\)。(但是個人都不會讓他等於正無窮吧?)

求這 \(n\) 個人等待時間之和最小值。

首先,觀察資料範圍

小資料就不說了吧

顯然,可以根據時間設定狀態。

設 \(f_\) 表示前 \(i\) 個時間單位的最小花費。

題上說了,擺渡車容量可以視為無限大,那麼我們可以知道,擺渡車是印度產的,在時間 \(i\) 出發的車可以將在 \(i\) 及之前到站所有人都接走,花費為\(\sum (i-t_) \ \ \ (t_\le i)\)。

如果在 \(i\)之前的某時刻 \(j\),擺渡車接過一次人呢?(根據題意 \(j\) 應當滿足 \(0 \le j \le i-m\) )。

那麼就有 \(f_=min(f_,f_+\sum (i-t_))\ \ (j < t_\le i)\) 。

這就是狀態轉移方程,可以發現遍歷一段時間是 \(o(t)\) 的,列舉斷點 \(j\) 也是 \(o(t)\) 的,而計算 \(\sum (i-t_)\) 是 \(o(n)\) 的。

這個dp時間複雜度為 \(o(nt^)\)。(恭喜你拿到我沒截上屏的30分)

優化計算

\(\sum (i-t_)\) 是顯然能用字首和搞一下的:

設 \(cnt_\) 表示時間 \(i\) 及之前有多少人在到達。

\(sum_\) 表示若在 \(i\) 之前來的人一直在等,那麼這些人一共等了多久,\(sum_\) 可以通過 \(cnt_\) 累加得到。

n=qr();//讀入和處理cnt和sum部分

m=qr();

for(register int i=1;i<=n;i++)

for(register int i=1;i那麼怎麼通過 \(cnt\) 和 \(sum\) 計算出 \(\sum (i-t_)\ \ \ (j < t_\le i)\) 呢?

請看圖。

用水平線長短及端點來表示人的等待情況,我們要求的是藍色線的總長度。

根據我畫的圈圈,\(1=sum_\) , \(2=sum_\) , \(3=cnt_*(i-j)\)。

那麼就可以輕而易舉地得到 \(\sum (i-t_) = sum_-sum_ - cnt_*(i-j)\ \ \ (j < t_\le i)\)。

狀態轉移方程就能轉化為

\(f_=min(f_,f_+sum_-sum_ - cnt_*(i-j))\ \ (j\le i-m)\) 。

於是複雜度愉快地變成了 \(o(t^)\) 。(現在有50pts了!)。

code

#include#define n 1100006

#define ll long long

using namespace std;

int n,m;

ll f[n];

int t,cnt[n],sum[n],ed;

inline int qr()

while(a<='9'&&a>='0')

return x*w;

}int main()

for(register int i=1;i規避無用轉移

顯然,每次擺渡車的往返加等待時間不會超過 \(2m\) ,因為該時間超過 \(2m\) 時,在\(i-(m,2m)\)可以來一輛車,答案必定不會更劣。

所以,列舉 \(j\) 時只用列舉 \([max(0,i-2m),i-m]\)就可以了,複雜度變成了 \(o(tm)\)。

把轉移改一下就有70pts了!(開o2就過了)。

code

for(register int i=1;i尖端科技——斜率優化(霧)

重新看一下式子:

\(f_=min(f_,f_+sum_-sum_ - cnt_*(i-j))\)

把min去掉,然後移下項,可以得到:

\(f_+cnt_*j-sum_=cnt_*i+f_-sum_\)

使 \(y=f_+cnt_*j-sum_\) , \(k=i\) , \(x=cnt_\) , \(b=f_-sum_\)

於是轉移方程轉化愉快地為 \(y=kx+b\) 的點斜式形式。

這裡 \(x\) 和 \(k\) 具有點調性,可以用優先佇列維護下凸包。

當遍歷到時間 \(i\) 時將 \(i-m\) 入隊,如果佇列非空進行轉移,如果佇列是空的話,直接當做之前沒有發過車對 \(f_\) 賦值就行了。

code

#include#define n 9100006

#define ll long long

#define lb long double

using namespace std;

int n,m;

ll f[n],t,cnt[n],sum[n],q[n],ed;

inline int qr()

while(a<='9'&&a>='0')

return x*w;

}inline lb k(ll x,ll y)//計算兩點間斜率

int main()

for(register int i=1;i=m)

while(l

l++;

f[i]=sum[i];//前面沒有發車

if(l<=r)

f[i]=min(f[i],f[q[l]]+sum[i]-sum[q[l]]-cnt[q[l]]*(i-q[l]));

} ll ans=0x3f3f3f3f3f3f3f3f;

for(register int i=ed;i

ans=min(ans,f[i]);

printf("%lld\n",ans);

return 0;

}

Noip2018普及 擺渡車

生涯回憶錄徹底摸了。反正也沒啥人看 過幾天要給普及組神犇講課就尋思做一點普及題,然後差點被錘爆,題在luogu上找的不貼了 首先考慮如果前後兩個人等待時間相差超過 m 往返時間 那一定不必等,因為中間完全可以送一趟。所以我們發現這個 4e6 的 t i 的資料範圍一定沒用.而且時間肯定要排序一下啦。...

NOIP2018普及組 參賽總結

發現就我只打了這麼一點字,所以再重新發一下 考前 考試前一天晚上一直在看電視 看到了10點還是11點吧,寫了一會兒會兒作業,但我覺得這不是考砸了的理由 誰叫我本來就如此之水 第二天早上很早就起床了,還順便跟外婆還有老媽去小區外面吃了個早餐。然後老媽帶我來了考場,也是在附近萬達吃了飯,走路就花了我差不...

NOIP2018普及組複賽解析

輸入乙個字串,求字串除了空格的字元個數 這種考你會不會程式設計的題不會?include include include using namespace std int ans string c intmain 乙個長度為n序列,被中間點m分成兩半,m左邊和m右邊。左邊戰鬥力為 i 1m 1 m i ...