斜率最佳化dp

Livedremyhy發表於2024-05-30

先看模板題:

洛谷 P5785 [SDOI2012] 任務安排

弱化版就不說了這裡放個轉移方程,記 \(pret=\sum_{i=1}^n t_i,prec=\sum_{i=1}^n c_i\)

\[f_i=\min_{j=0}^{i-1}(f_j+pret_i\times (prec_i-prec_j)+s\times (prec_n-prec_j)) \]

答案為 \(f_n\),把轉移方程裡含 \(i,j,i\times j\) 的項分開:

\[f_i=\min_{j=0}^{i-1}(f_j-(s+pret_i)\times prec_j+pret_i\times prec_i+s\times prec_i) \]

再把 \(\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];
}

相關文章