[斜率優化] codefores 660F. Bear and Bowling 4

F. Bear and Bowling 4
題意:
給一個序列val ,任選連續的一段[l,r] ,其價值爲rj=lval[j](jl+1) ,求最大價值 。
簡單的說就是可以去掉這個序列的某前綴和某後綴,然後對新得到的valans=val[i]i ,最後求max(ans)
題解:
斜率優化,這個blog前面講的不錯。
花了兩天才完全搞懂這個題。
怎麼得出來的呢,我們一步一步來。
首先令sum[i]=ij=1val[j] ,令p[i]=ij=1val[j]j
然後我們就可以表示出任意[l,r] 的答案,令爲ans[l,r]=p[r]p[l1](l1)(sum[r]sum[l1]),l[1,r]
注意到裏面ll1 其實是一一對應的,爲了方便,令ans[a,b]=p[b]p[a]a(sum[b]sum[a]),a[0,b1]
這樣可以得到一個O(n2) 的暴力。

下面來優化。

設有任意三點k<j<i ,同時假設ans[j,i]>ans[k,i] ,把上面的ans 代進去,我得到的結果如下:

(p[k]ksum[k])(p[j]jsum[j])jk>sum[i]

過程略,可以自行驗證。

爲了看起來簡單,令y(x)=(p[x]xsum[x]) ,那麼上面可以寫成如下形式:

y(j)y(k)jk>sum[i]

最初我推出的是這個式子,然而我用這個去維護卻無法ac,後來我把左邊完全化爲斜率形式:
g(j,k)=y(j)y(k)jk<sum[i]

顯然g(j,k) 可以看做jk 的斜率。
前面我們假設j>k 並且ans[j,i]>ans[k,i] 結果得到了這個式子。
結論就是,對於任意固定的i ,如果有j>kg(j,k)<sum[i] ,便可以得出對於這個i ,選擇j 要比選擇k 更好。

然而這樣還沒有得出如何優化,只得到了一個判斷誰更優的方法。

同樣假設k<j<i ,如果滿足g(i,j)<g(j,k) ,會發生什麼情況呢?
如果g(i,j)<sum[i] ,根據上面的結論,i 是優於j 的。
如果g(i,j)>sum[i] ,那麼有g(j,k)>g(i,j)>sum[i] ,根據上面的結論,雖然j 優於i ,但是有k 優於j

結論就是,如果存在k<j<ig(i,j)<g(j,k) ,那麼j 永遠不會成爲最優解,因爲左邊有k ,右邊有i

所以我們去除所有這樣的j 之後,也就是不存在g(i,j)<g(j,k) 了。
本來對於一個i ,爲了求max(ans(l,i)) ,應該在i 左邊所有點裏找到最優的l ,現在去掉了不可能最優的點,這就是優化。
根據斜率來看,也就是任意三個點k<j<i ,滿足kj 的斜率小於ji 的斜率,整個曲線斜率遞增,導數是爲正的,形象一點可以想象f(x)=x2 的曲線。

這種優化叫做斜率優化,它和幾何斜率密切相關,膜一發CDQ女神。

現在對於一個i ,已經知道了max(ans(l,i)) 中,l 的解集,並且已經把不可能的點都從解集中去掉了,如何快速求出最優的l 呢?
因爲sum[i] 不是單調的,如果維護隊首,可能會把後面需要的點出隊。
根據我們維護的斜率的單調性,有一種二分的方法。
假設l 的解集爲a1,a2,a3,,an ,對於任意k<j<i ,根據前面的結論,如果滿足g(aj,ak)<sum[i] ,那麼aj 優於ak
於是二分的時候,計算對於一個mid ,是否滿足g(amidamid1)<sum[i]
滿足,說明amidamid1 ,那麼答案應該在mid的右邊。
不滿足,說明amid1amid ,那麼答案應該在mid的左邊。
二分的正確性在於我們已經維護好了g(ai,aj) 單調遞增。
容易發現我們是在解集裏求一個極值點pos ,滿足g(apos,apos1)<sum[i]g(apos+1,apos)>sum[i]
顯然求極值同樣可以採用三分法。
到這裏,此題已經算是解決了,可喜可賀,收穫頗豐。
再加一個斜率優化DP的題目:here
附代碼:

#include<stdio.h>
#include<algorithm>
using std::max;
typedef long long ll;
const int N = 2e5+5;
ll val[N], sum[N] = {0}, p[N] = {0};
int q[N], top, tail;
inline ll y(int x){ return p[x] - x*sum[x]; }
double g(int j, int k){
    double dy = y(j) - y(k);
    double dx = j - k;
    return dy/dx;
}
inline ll getans(int i, int j){
    return p[i] - p[j] - j*(sum[i] - sum[j]);
}
int solve(ll x){
    int l = top, r = tail-1, mid, res = l;
    while(l <= r){ //根據斜率二分求最優點
        mid = (l+r) >> 1;
        if(g(q[mid], q[mid-1]) < -x) l = mid+1, res = mid; 
        else r = mid-1;
    }
    return q[res];
}
int main(){
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i){
        scanf("%lld", val+i);
        sum[i] = sum[i-1] + val[i];
        p[i] = p[i-1] + i*val[i];
    }
    top = tail = 0;
    q[tail++] = 0;
    ll ans = 0;
    for(int i = 1; i <= n; ++i){
        int j = solve(sum[i]); //對於固定的i,二分求最優點
        ans = max(ans, getans(i,j)); //更新答案
        while(top < tail-1 && g(i, q[tail-1]) < g(q[tail-1], q[tail-2])) tail--;  //滿足了g(i,j)<g(j,k)
        q[tail++] = i;
    }
    printf("%lld\n", ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章