先看模板題:
洛谷 P5785 [SDOI2012] 任務安排
弱化版就不說了這裡放個轉移方程,記 \(pret=\sum_{i=1}^n t_i,prec=\sum_{i=1}^n c_i\):
答案為 \(f_n\),把轉移方程裡含 \(i,j,i\times j\) 的項分開:
再把 \(\min\) 幹掉,分開變數常量:
\(f_j=(s+pret_i)\times prec_j+f_i-pret_i\times prec_i-s\times prec_i\)。
發現這個東西很像一次函式解析式:\(y=kx+b\),在以 \(prec_j\) 為橫座標,\(f_j\) 為縱座標的直角座標系裡,\(y=f_j,k=s+pret_i,b=f_i-pret_i\times prec_i-s\times prec_i\),讓 \(f_i\) 最小即讓截距 \(b\) 最小。
整理問題:每個 \(j\) 都在直角座標系裡對應點 \((prec_j,f_j)\),有一條斜率固定的直線不斷向上平移,接觸點後停止,求直線截距(這樣截距肯定最小),考慮如何求解這個問題。
發現下圖這樣的上凸點一定無法成為最先接觸的點,把這些點移去剩下一個下凸包,對於一條斜率為 \(k\) 的直線,如果一個點 \(x\) 左邊線段(維護後)斜率小於 \(k\) 右邊線段斜率大於 \(k\),那麼直線最先接觸到 \(x\) 點。
下面分析幾種不同形式。
若 \(0\le T_i\le 2^8,0\le C_i\le 2^8\)
由於斜率 \(k\) 值單調遞增,點的橫座標也單調遞增,可以用單調佇列來維護下凸包,記隊頭為 \(h\),隊尾為 \(t\)。
若 \(q_h,q_{h+1}\) 組成線段斜率小於等於當前斜率 \(k\),由於 \(pret\) 單調遞增,之後斜率只可能更大,所以在之後任何直線不會先接觸到 \(q_h\),彈出隊頭,重複以上操作直到不能操作,這時取 \(q_h\) 即為最優。
若在隊尾插入 \(i\) 後 \(q_t\) 上凸,則彈出隊尾,重複以上操作直到不能操作後在隊尾插入 \(i\)(維護凸包)。
程式碼咕咕咕。
若 \(\left| T_i\right|\le 2^8,0\le C_i\le 2^8\)
這時斜率 \(k\) 值不單調遞增,必須維護整個凸包,但凸包中線段斜率依然單調遞增,可以二分查詢最優的點,維護凸包方法不變。
//出題人卡斜率精度,最好移項變成乘法
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL kMaxN = 3e5 + 5;
LL n, s, t = 1, q[kMaxN], prec[kMaxN], pret[kMaxN], f[kMaxN];
int F(int k) {
int l = 0, r = t;
for (; l + 1 < r;) {
int mid = (l + r) / 2;
f[q[mid + 1]] - f[q[mid]] <= k * (prec[q[mid + 1]] - prec[q[mid]]) ? l = mid : r = mid;
}
return q[r];
}
signed main() {
cin.tie(0)->ios::sync_with_stdio(0), cin >> n >> s;
for (LL i = 1, t, c; i <= n; i++) {
cin >> t >> c, pret[i] = pret[i - 1] + t, prec[i] = prec[i - 1] + c;
}
for (int i = 1; i <= n; i++) {
int p = F(s + pret[i]);
f[i] = f[p] - (s + pret[i]) * prec[p] + pret[i] * prec[i] + s * prec[n];
for (; t > 1 && (f[q[t]] - f[q[t - 1]]) * (prec[i] - prec[q[t]]) >=
(f[i] - f[q[t]]) * (prec[q[t]] - prec[q[t - 1]]); t--) {
}
q[++t] = i;
}
cout << f[n];
}