斜率優化c++
適用範圍:
markdown
斜率優化適用於dp狀態較容易維護且決策點與全局直接無關的dpapp
例如:函數
f[i]=min(f[k]+a[k]*a[i]);
這裏含有a[k]*a[i]這一項,因此不能簡單用單調隊列根據決策點的權值來判斷,要使用斜率優化優化
使用:spa
舉出一個方程式:
code
f[i]=min(f[k]+(sum[i]-sum[k])^2+c);
化簡得:blog
f[i]=min(f[k]+sum[i]^2+sum[k]^2-2*sum[i]*sum[k]+c)
將min函數去掉,把含只含k的項移到等式左邊,含i,k的項移到等號右邊,只含i的項和常數移到前者右邊,獲得:
隊列
f[k]+sum[k]^2=2*sum[i]*sum[k]-sum[i]^2-c+f[i]
將此式看做一次函數:Y=KX+Bit
Y=f[k]+sum[k]^2 K=2*sum[i]; X=sum[k] B=-sum[i]^2-c+f[i]
long long X(long long k) { return ... } long long Y(long long k) { return ... }(聲明函數名)
至關於Y,K,X已知權值,咱們只需找到一個最合適的X使B最小就好了
那麼如何優化?
發現三個點k1,k2,k3
若是連結k1,k2直線的斜率比連結k2,k3直線的斜率大,那麼k2一定是無用點,去除k2
這樣一來,斜率一定是單調上升的,能夠通用單調隊列來維護這些點
long long check2(long long x, long long y, long long z) { return (Y(y) - Y(x)) * (X(z) - X(y)) > (X(y) - X(x)) * (Y(z) - Y(y)); }
針對全部函數Y=KX+B分3種狀況討論
1:K,X都有單調性
用單調隊列維護,用每一個i對應的K來更新隊頭,再取出取出隊頭做爲決策點,再踢無用隊尾,加入i點,時間複雜度O(n)
long long check1(long long x, long long y, long long k) { return (Y(y) - Y(x)) < (X(y) - X(x)) * k; }
long long head = 1, tail = 0; t[++tail] = 0; for (long long i = 1; i <= n; i++) { long long K = ...(i所對應的斜率) while (head < tail && check1(t[head], t[head + 1], K)) head++; f[i] = f[t[head]] + (sum[i] - sum[t[head]]) * (sum[i] - sum[t[head]]) + c; while (head < tail && check2(t[tail - 1], t[tail], i)) tail--; t[++tail] = i; }
2:K無單調性,X有單調性,用二分找出最優勢,無需踢隊頭,其他步驟同樣,時間複雜度O(nlogn)
long long find(long long k) { long long l = head, r = tail; while (l < r) { long long mid = (l + r) >> 1; if (check1(t[mid], t[mid + 1], k)) l = mid + 1; else r = mid; } return t[l]; }
t[++tail] = 0; for (long long i = 1; i <= n; i++) { long long K = sumt[i]; long long pre = find(K); f[i] = f[pre] + (sumc[n] - sumc[pre]) * (s + sumt[i] - sumt[pre]); while (head < tail && check2(t[tail - 1], t[tail], i)) tail--; t[++tail] = i; }
3:x沒有單調性,這樣必須動態差點,須要用splay,坑之後再填
例題:
#include < bits / stdc++.h > using namespace std; const long long N = 3e5 + 10; long long t[N], c[N], sumt[N], sumc[N], f[N]; long long n, s; long long head = 1, tail = 0; long long X(long long k) { return sumc[k]; } long long Y(long long k) { return f[k] - sumc[n] * sumt[k] - sumc[k] * s + sumc[k] * sumt[k]; } long long check1(long long x, long long y, long long k) { return (Y(y) - Y(x)) <= (X(y) - X(x)) * k; } long long check2(long long x, long long y, long long z) { return (Y(y) - Y(x)) * (X(z) - X(y)) >= (X(y) - X(x)) * (Y(z) - Y(y)); } long long find(long long k) { long long l = head, r = tail; while (l < r) { long long mid = (l + r) >> 1; if (check1(t[mid], t[mid + 1], k)) l = mid + 1; else r = mid; } return t[l]; } int main() { memset(f, 0x3f, sizeof(f)); f[0] = 0; scanf("%lld%lld", &n, &s); for (long long i = 1; i <= n; i++) scanf("%lld%lld", &t[i], &c[i]); for (long long i = 1; i <= n; i++) sumt[i] = sumt[i - 1] + t[i]; for (long long i = 1; i <= n; i++) sumc[i] = sumc[i - 1] + c[i]; t[++tail] = 0; for (long long i = 1; i <= n; i++) { long long K = sumt[i]; long long pre = find(K); f[i] = f[pre] + (sumc[n] - sumc[pre]) * (s + sumt[i] - sumt[pre]); while (head < tail && check2(t[tail - 1], t[tail], i)) tail--; t[++tail] = i; } printf("%lld\n", f[n]); }
總結:
針對斜率優化的dp,只要將方程化簡後找出k,x的單調性分狀況討論套模板便可