由於 t 並不大,考慮以 t 爲狀態進行 dp。(這題有 的優秀dp做法,這裏只爲了練習斜率優化dp)
維護 cnt[i] 表示前 i 時刻的人數
,sum[i] 表示前 i 時刻所有人的下標之和
。
設 dp[i] 表示在第 i 時刻發車,所有人等車的最小時間
,顯然最後答案分佈在 。
列出轉移方程:dp[i] = dp[j] + i * (cnt[i] - cnt[j]) - (sum[i] - sum[j]))
展開得到:dp[i] = dp[j] + i * cnt[i] - i * cnt[j] - sum[i] + sum[j]
,兩邊移項得到:
dp[j] + sum[j] = i * cnt[j] + dp[i] + i * cnt[i]
,出現了 i * cnt[j]
,且橫座標 cnt[j]
單調遞增,可以用斜率優化。
由於斜率 i
也具有單調性,因此決策具有單調性,用單調隊列維護一個下凸包,每次將 i - m
維護到隊列中,就可以保證 下標差 >= m
。
什麼時候維護下凸包:當橫座標單增,要求的是最小值時,維護下凸包,要求的是最大值時維護上凸包。若橫座標單減,則求最小值時維護的是上凸包,求最大值時維護的是下凸包(指在二維平面上的呈現)
有個坑點就是 可以爲 0,不做處理的話,以 0 爲轉移點會出錯,可以將時間全部 + 1。
代碼:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e6 + 500;
typedef long long ll;
const ll inf = 1e18;
int v[maxn],n,m,tot,N;
ll dp[maxn],sum[maxn];
int q[maxn],front,rear;
ll calc(int x,int y) {
return dp[y] + 1ll * x * (v[x] - v[y]) - (sum[x] - sum[y]);
}
ll getY(int x) {
return dp[x] + sum[x];
}
ll getX(int x) {
return v[x];
}
int main() {
scanf("%d%d",&n,&m);
for (int i = 1,x; i <= n; i++) {
scanf("%d",&x);
x++;
v[x]++; //人數前綴和
sum[x] += x;
N = max(N,x + m);
}
for (int i = 1; i <= N; i++) {
sum[i] += sum[i - 1]; //時間前綴和
v[i] += v[i - 1];
}
for (int i = 0; i < m; i++)
dp[i] = calc(i,0);
for (int i = m; i <= N; i++) {
while (front + 1 < rear && (getY(i - m) - getY(q[rear])) * (getX(q[rear]) - getX(q[rear - 1]))
<= (getY(q[rear]) - getY(q[rear - 1])) * (getX(i - m) - getX(q[rear])))
rear--;
q[++rear] = i - m;
while (front + 1 < rear && calc(i,q[front + 1]) >= calc(i,q[front + 2]))
front++;
dp[i] = calc(i,q[front + 1]);
}
ll ans = dp[N];
for (int i = N - m; i <= N; i++)
ans = min(ans,dp[i]);
printf("%lld\n",ans);
return 0;
}