1. 程式人生 > >[ HNOI 2008 ] 玩具裝箱

[ HNOI 2008 ] 玩具裝箱

\(\\\)

Description


現有編號為 \(1\sim N\)\(N\) 件玩具,第 \(i\) 件長度為 \(C_i\)

現在要把按順序排好的玩具劃分成若干段,$[l,r] $的長度是 \(len_{\ l\sim r}=r-l+\sum_{i=l}^r C_i\)

一段的代價為 \((len-L)^2\) ,其中 \(L\) 是給出的常量。

求最小總代價。

  • \(N\le 5\times10^4,C_i,L\le 10^9\)

\(\\\)

Solution


\(len(l,r)\) 表示區間 \((l,r]\) 新成一段的最後長度。

顯然有 \(len(l,r)=r-l-1+\sum_{i=l+1}^rC_i\)

記錄 \(sum[i]=\sum_{j=1}^iC_j\) ,那麼有
\[ len(l,r)=r+sum[r]-(l+sum[l])-1 \]
發現式子可以表示成 \(l,r\) 相關,我們設 \(t(i)=i+sum[i]\) ,那麼有
\[ len(l,r)=t(r)-t(l)-1 \]
我們設 \(w(l,r)\) 表示區間 \((l,r]\) 新生成一段的代價,那麼有
\[ w(l,r)=\bigg(len(l,r)-L\bigg)^2=\bigg(t(r)-t(l)-1-L\bigg)^2 \]
不妨再設 \(g(i)=t(i)+1+L\)

,那麼有 \(w(l,r)=\bigg(t(r)-g(l)\bigg)^2=t(r)^2+g(l)^2-2g(l)t(r)\)

顯然 \(t\)\(g\) 是可以 \(O(n)\) 預處理的,詢問可以做到 \(O(1)\)

\(\\\)

\(f[i]\) 表示到第 \(i\) 個玩具為止 \((\) 含第 \(i\)\()\),全部裝箱的最小代價。
\[ f[i]=\min_{j=1}^{i-1}\{f[j]+w(j,i)\}=\min_{j=1}^{i-1}\{f[j]+t(i)^2+g(j)^2-2g(j)t(i)\} \]

假設我們現在找到了最優轉移點 \(j\)

,那麼有
\[ f[i]=f[j]+t(i)^2+g(j)^2-2g(j)t(i) \]
移項,有
\[ f[j]+g(j)^2=f[i]-t(i)^2+2t(i)g(j) \]
此時我們可以將轉移模型抽象到二維平面了,設點 \((g(j),f[j]+g(j)^2)\) 表示一個狀態 。

我們將 \(2t(i)\) 看作斜率,將 \(f[i]-t(i)^2\) 看作與 \(y\) 軸相交點的縱座標。

目標明確了:選擇之前在座標系上的任意一點用斜率構建直線,使得這一縱座標最小。

\(\\\)

因為 \(C_i\ge 1\) 所以 \(t(i)\ge 0\) ,我們的答案顯然只會產生在下凸殼上。

如果我們用 \(j\) 去刻畫 \(f[j]\) 對應的狀態,考慮對於一個狀態 \(i\) 的更新,\(j\) 點的答案合適劣於 \(k\) 點的答案。

移項,把 \(j,k\) 的答案都帶進去,列出不等式,化簡,可以得到 \((\) 博主太懶 \():\)
\[ f[k]+g(k)^2-2g(k)t(i)\le f[j]+g(j)^2-2g(j)t(i)\\ \frac{f[k]+g(k)^2-f[j]-g(j)^2}{g(k)-g(j)}\le 2t(i) \]
繼續分析,還是因為 \(C_i\ge 1\) ,所以 \(t(i)\) 是單調不降的。

因此注意到不等式右側是單調不降的,所以每一個位置的最優決策點 \(j\) 單調不減。

用佇列維護點,隊頭用單調佇列那一套就可以搞了。

關於下凸包的維護,可以考慮隊尾是一個單調棧,每次按照斜率逐個彈棧就可以了。

\(\\\)

特別提醒:此做法中 \(g(0)\) 初值不為 \(0\) ,而轉移要用到,在開始的時候記得設 \(g(0)=1+L\)

其實設 \(g(i)=i+m,t(i)=g(i)-1-L\) ,可以驚喜的發現,後面所有的式子都是不變的。

而且也不需要預處理初值,因為轉移方程在沒有 \(t(j)\) 這一項,而在這一設法中, \(g(0)=0\)

\(\\\)

Code


#include<cmath>
#include<cctype>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 50010
#define R register
#define gc getchar
using namespace std;
typedef long long ll;

inline ll rd(){
  ll x=0; bool f=0; char c=gc();
  while(!isdigit(c)){if(c=='-')f=1;c=gc();}
  while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
  return f?-x:x;
}

ll n,m,t[N],g[N],f[N],q[N],hd,tl;

inline ll w(ll x,ll k){
  return f[x]+k*k+g[x]*g[x]-2*k*g[x];
}

inline double calck(ll x,ll y){
  return (double)(f[x]+g[x]*g[x]-f[y]-g[y]*g[y])/(double)(g[x]-g[y]);
}

int main(){
  n=rd(); m=rd();
  for(R int i=1,sum=0;i<=n;++i){
    sum+=rd();
    t[i]=i+sum; g[i]=t[i]+1+m;
  }
  q[hd=tl=1]=0;
  g[0]=1+m;
  for(R int i=1;i<=n;++i){
    while(hd<tl&&calck(q[hd+1],q[hd])<(double)t[i]*2) ++hd;
    f[i]=w(q[hd],t[i]);
    while(hd<tl&&calck(q[tl],q[tl-1])>calck(i,q[tl])) --tl;
    q[++tl]=i;
  }
  printf("%lld\n",f[n]);
  return 0;
}