斜率優化學習筆記

黃牛名字太搞笑發表於2021-01-03

              斜率優化

適用範圍:

斜率優化適用於dp狀態較容易維護且決策點與全域性直接無關的dp

例如:

f[i]=min(f[k]+a[k]*a[i]);

這裡含有a[k]*a[i]這一項,所以不能簡單用單調佇列根據決策點的權值來判斷,要使用斜率優化

使用:

舉出一個方程式:

f[i]=min(f[k]+(sum[i]-sum[k])^2+c);

化簡得:

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+B

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,坑以後再填

例題:

[SDOI2012]任務安排

#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的單調性分情況討論套模板即可

 

 

 

 

 

 

 

  

 

相關文章