1. 程式人生 > >P3195 [HNOI2008]玩具裝箱TOY(斜率優化dp)

P3195 [HNOI2008]玩具裝箱TOY(斜率優化dp)

玩具 eight sin nbsp https org 問題 div 前綴和

P3195 [HNOI2008]玩具裝箱TOY

設前綴和為$s[i]$

那麽顯然可以得出方程

$f[i]=f[j]+(s[i]-s[j]+i-j-L-1)^{2}$

換下順序

$f[i]=f[j]+(s[i]+i-(s[j]+j+L+1))^{2}$

為了處理方便,我們套路地設

$a[i]=s[i]+i$

$b[i]=s[i]+i+L+1$

於是得出

$f[i]=f[j]+(a[i]-b[j])^{2}$

拆開:$f[i]=f[j]+a[i]^{2}-2*a[i]*b[j]+b[j]^{2}$

移項:$f[j]+b[j]^{2}=2*a[i]*b[j]+f[i]-a[i]^2$

於是我們就把不變量和變量分開了($i$固定)

仔細觀察

$f[j]+b[j]^{2}=2*a[i]*b[j]+f[i]-a[i]^2$

$y=k*x+b$

一次函數!

$y=f[j]+b[j]^{2}$

$k=2*a[i]$($i$遞增時,顯然它是單調遞增的)

$x=b[j]$

$b=f[i]-a[i]^{2}$

如果我們要讓$f[i]$最小,就是讓$b$最小

而對於每個$i$,$k$是不變的

那麽問題就轉化成:找到一個最優的$(x,y)$使$b$最小

考慮到$k$是單調遞增的

於是我們就可以快樂地用單調隊列維護下凸包

while(L<R&&K(h[L],h[L+1
])<=2*a(i)) ++L;//顯然h[L]不比h[L+1]優,可以刪去 f[i]=f[h[L]]+(a(i)-b(h[L]))*(a(i)-b(h[L]));//計算出最優的f[i] while(L<R&&K(h[R-1],h[R])>K(h[R],i)) --R;//加入點(x[i],y[i])後,h[R]在凸包內部,可以刪去① h[++R]=i;//入隊

①:顯然在加入橙點後,藍點在凸包內部,可以被刪除

技術分享圖片

#include<iostream>
#include<cstdio>
#include<cstring>
using
namespace std; typedef double db; #define N 50005 db f[N],s[N]; int n,l,L,R,h[N]; inline db a(int x){return s[x]+x;} inline db b(int x){return s[x]+x+l+1;} inline db X(int x){return b(x);} inline db Y(int x){return f[x]+b(x)*b(x);} inline db K(int x,int y){return (Y(x)-Y(y))/(X(x)-X(y));} int main(){ scanf("%d%d",&n,&l); for(int i=1;i<=n;++i) scanf("%lf",&s[i]),s[i]+=s[i-1]; L=R=1; for(int i=1;i<=n;++i){ while(L<R&&K(h[L],h[L+1])<=2*a(i)) ++L; f[i]=f[h[L]]+(a(i)-b(h[L]))*(a(i)-b(h[L])); while(L<R&&K(h[R-1],h[R])>K(h[R],i)) --R; h[++R]=i; }printf("%.0lf",f[n]); return 0; }

P3195 [HNOI2008]玩具裝箱TOY(斜率優化dp)