洛谷P4072 [SDOI2016]征途(帶權二分,斜率優化)

洛谷題目傳送門html

一開始確定要把題目要求的式子給寫出來函數

咱們知道方差的公式\(s^2=\frac{\sum\limits_{i=1}^{m}(x_i-\overline x)^2}{m}\)優化

題目要乘\(m^2\)再輸出,因而spa

\(m^2s^2=m\sum\limits_{i=1}^{m}(x_i-\overline x)^2\)code

\(=m(\sum\limits_{i=1}^{m}x_i^2-2\overline{x}\sum\limits_{i=1}^{m}x_i+m\overline{x}^2)\)htm

\(=m\sum\limits_{i=1}^{m}x_i^2-(\sum\limits_{i=1}^{m}x_i)^2\)blog

因而只要最小化\(\sum\limits_{i=1}^{m}x_i^2\)便可。隊列

然而選\(m\)段很是很差辦。這時候能夠聯想到凸優化。設\(G_m\)表示選\(m\)\(\sum\limits_{i=1}^{m}x_i^2\)的最小值,當\(m\)增大的時候\(G_m\)顯然會減少,憑蒟蒻的感性理解,多分出一段對答案的影響幅度也愈來愈小,也就是說\(G_x\)關於\(x\)的函數圖像大概是下凸的。get

咱們用一個斜率爲\(mid\)的直線去切這個凸包。顯然\(mid\)的下界取\([0,1]\)之間的斜率,是總路程平方級別的,上界是\(0\)。由於切線在凸包的下方,因此多選一段的代價不是\(+mid\)而是\(-mid\)flash

update:蒟蒻棄用了用直線切凸包的理解方法,蒟蒻用導數思想理解DP凸優化的思路能夠看這裏

接下來就是斜率優化的過程。設\(f_i\)爲前\(i\)條路的最優答案,\(x_i\)爲路程長度的前綴和,寫出轉移方程

\(f_i=\min\limits_{j=0}^{i}\{f_j-2x_ix_j+x_j^2\}+x_i^2\)

決策\(j\)優於\(k\)當且僅當

\(f_j-2x_ix_j+x_j^2<f_k-2x_ix_k+x_k^2\)

\(\frac{f_j+x_j^2-f_k-x_k^2}{x_j-x_k}<2x_i\)

因而設\(y_i=f_i+x_i^2\),把決策當作點\((x_i,y_i)\),使用單調隊列就OK了。注意這裏要記\(c_i\)表示最優決策下將前\(i\)條路分出的段數。最後判斷\(c_n\)\(m\)的關係來調整斜率。

因爲這一題的斜率確定不會有小數,故也沒必要擔憂二分中的一些邊界問題。

#include<cstdio>
#define RG register
#define R RG int
#define G c=getchar()
#define Calc(j,k) (y[j]-y[k])/(x[j]-x[k])
typedef long long LL;
const int N=3009;
int n,q[N],c[N];
double f[N],k[N],x[N],y[N];
inline int in(){
    RG char G;
    while(c<'-')G;
    R x=c&15;G;
    while(c>'-')x=x*10+(c&15),G;
    return x;
}
inline double sqr(RG double x){
    return x*x;
}
inline void work(R mid){//斜率優化
    R h,t,i;
    for(h=t=i=1;i<=n;++i){
        while(h<t&&k[h]<2*x[i])++h;
        f[i]=f[q[h]]+sqr(x[i]-x[q[h]])-mid;//每轉移一次要減一下mid
        y[i]=f[i]+sqr(x[i]);
        c[i]=c[q[h]]+1;//記錄段數
        while(h<t&&k[t-1]>Calc(q[t],i))--t;
        k[t]=Calc(q[t],i);q[++t]=i;
    }
}
int main(){
    n=in();R m=in(),l,r,mid,i;
    for(i=1;i<=n;++i)x[i]=x[i-1]+in();
    l=-sqr(x[n]);r=0;//大體肯定下界
    while(l<r){
        work(mid=(l+r+1)/2);//注意負數的下取整問題
        c[n]<=m?l=mid:r=mid-1;
    }
    work(l);
    printf("%.0lf\n",m*(f[n]+m*l)-sqr(x[n]));//先加回m*l
    return 0;
}
相關文章
相關標籤/搜索