F. Bear and Bowling 4
題意:
給一個序列
簡單的說就是可以去掉這個序列的某前綴和某後綴,然後對新得到的
題解:
斜率優化,這個blog前面講的不錯。
花了兩天才完全搞懂這個題。
怎麼得出來的呢,我們一步一步來。
首先令
然後我們就可以表示出任意
注意到裏面
這樣可以得到一個
下面來優化。
設有任意三點
過程略,可以自行驗證。
爲了看起來簡單,令
最初我推出的是這個式子,然而我用這個去維護卻無法ac,後來我把左邊完全化爲斜率形式:
顯然
前面我們假設
結論就是,對於任意固定的
然而這樣還沒有得出如何優化,只得到了一個判斷誰更優的方法。
同樣假設
如果
如果
結論就是,如果存在
所以我們去除所有這樣的
本來對於一個
根據斜率來看,也就是任意三個點
這種優化叫做斜率優化,它和幾何斜率密切相關,膜一發CDQ女神。
現在對於一個
因爲
根據我們維護的斜率的單調性,有一種二分的方法。
假設
於是二分的時候,計算對於一個
滿足,說明
不滿足,說明
二分的正確性在於我們已經維護好了
容易發現我們是在解集裏求一個極值點
顯然求極值同樣可以採用三分法。
到這裏,此題已經算是解決了,可喜可賀,收穫頗豐。
再加一個斜率優化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);
}