1. 程式人生 > >P3648 [APIO2014]序列分割(斜率優化dp)

P3648 [APIO2014]序列分割(斜率優化dp)

api ostream 凸包 求解 ref ble urn clu 影響

P3648 [APIO2014]序列分割

我們先證明,分塊的順序對結果沒有影響。

我們有一個長度為3的序列$abc$

現在我們將$a,b,c$分開來

隨意枚舉一種分塊方法,如$(ab)(c)$,$(a)(b)(c)$

答案為$(a+b)*c+a*b=ac+bc+ab$

多枚舉幾種,我們發現答案總是不變的。

所以我們可以默認每次從左到右掃,用dp求解

對於每個$1$到$k$,我們都把序列掃一遍

設$f[k][i]$為對前$i$個數進行$k$次切割的最大價值,

$s[i]$為元素前綴和,那麽

$f[k][i]=f[k-1][j]+s[j]*(s[i]-s[j])$

這個$k$顯然是可以滾動優化掉的,設滾動數組為$g[i]$

$f[i]=g[j]+s[j]*(s[i]-s[j])$

$g[j]-s[j]^{2}=-s[i]*s[j]+f[i]$

又化成了我們熟悉的$y=kx+b$

$y=g[j]-s[j]^{2}$

$k=-s[i]$

$x=s[j]$

$b=f[i]$

$x,k$單調遞增,於是我們直接上單調隊列維護下凸包就好辣

順帶再開個數組記錄一下路徑就好了

#include<iostream>
#include<cstdio>
#include<cstring>
#define rint register int
using namespace
std; typedef long long ll; #define N 100005 ll s[N],f[N],g[N]; int n,k,L,R,h[N],p[205][N]; inline ll X(int x){return s[x];} inline ll Y(int x){return g[x]-s[x]*s[x];} inline ll KK(ll xa,ll ya,ll xb,ll yb){return ya*xb-yb*xa;} int main(){ scanf("%d%d",&n,&k); for(rint i=1;i<=n;++i) scanf(
"%lld",&s[i]),s[i]+=s[i-1]; for(rint j=1;j<=k;++j){ h[L=R=1]=0; for(rint i=1;i<=n;++i){ while(L<R&&KK(X(h[L+1])-X(h[L]),Y(h[L+1])-Y(h[L]),1,-s[i])>=0) ++L; f[i]=g[h[L]]+(s[i]-s[h[L]])*s[h[L]]; p[j][i]=h[L]; while(L<R&&KK(X(h[R])-X(i),Y(h[R])-Y(i),X(h[R])-X(h[R-1]),Y(h[R])-Y(h[R-1]))<=0) --R; h[++R]=i; }swap(g,f); } printf("%lld\n",g[n]);//註意答案在g上 for(rint i=k,d=p[k][n];i;--i,d=p[i][d]) printf("%d ",d); return 0; }

P3648 [APIO2014]序列分割(斜率優化dp)