1. 程式人生 > 其它 >洛谷 P3195 [HNOI2008] 玩具裝箱

洛谷 P3195 [HNOI2008] 玩具裝箱

連結:

P3195


題意:

給出 \(n\) 個物品及其權值 \(c\),連續的物品可以放進一個容器,如果將 \(i\sim j\) 的物品放進一個容器,產生的費用是 \(\left(j-i+\sum\limits_{k=i}^jc_k-L\right)^2\),其中 \(L\) 是一個給出的常數,現在需要把所有物品都放進容器,請你最小化總費用。


分析:

這是一道非常經典的好題,適合練習單調佇列優化和斜率優化dp。

我們設 \(sum[i]\) 表示物品權值的字首和,\(dp[i]\) 表示前 \(i\) 個物品的最小總費用,那麼有 \(O(n)\) 轉移:

\[dp[i]=\min \left(dp[j]+(i-(j+1)+sum[i]-sum[j]-L)^2\right) \]

我們將後面的式子化一下,把與 \(i\)

有關的和與 \(j\) 有關的拉出來,常數項隨意丟進裡面,

\[dp[i]=dp[j]+( (i+sum[i])-(j+sum[j]+L+1) )^2 \]

\(A(i)=i+sum[i],B(j)=j+sum[j]+L+1\)

\[dp[i]=dp[j]+A(i)^2-2A(i)B(j)+B(j)^2 \]

我們發現 \(A(i)\)\(B(j)\) 都是已知的,而 \(A(i)\) 只與當前位置有關,\(B(j)\) 只與之前的位置有關,可以視為決策。

由於存在 \(A(i)B(j)\) 這種既與當前位置有關,又與決策有關的東西,於是我們嘗試將與決策有關的東西單獨分離出來。我們對這個式子進行變換:

\[dp[j]+B(j)^2=2A(i)B(j)+dp[i]-A(i)^2 \]

可以將其視為一條斜率為 \(2A(i)\) 的直線,經過定點 \((B(j),dp[j]+B(j)^2)\),截距為 \(dp[i]-A(i)^2\)

我們成功將決策的資訊與整合到了一個點上!現在需要做的就是選擇一個最優的點,使得一條斜率一定的直線經過這個點時截距最小。

圖片摘自洛谷部落格


我們通過觀察可以發現,可能作為最優決策的點構成了一個下凸包(這在其他題目中可能不同),且對於一條斜率為 \(k\) 的直線,最優決策點是第一個滿足 \(slope(x,x+1)\geq k\) 的點。(\(slope\)

表示斜率)

用單調佇列維護凸包。同時注意到每次詢問的斜率 \(2A(i)\) 也是單調增的,於是對於找到最優決策點還可以用單調佇列優化。

注意到一個細節是要 "插入第0個點" 的資訊,否則無法將 \(1\sim i\) 放進一個容器。


演算法:

單調佇列維護下凸包,同時維護最優決策點,然後每次根據最優決策的資訊得到 \(dp[i]\),繼續維護凸包即可。時間複雜度 \(O(n)\)


程式碼:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
inline int read(){
	int p=0,f=1;
	char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){p=p*10+c-'0';c=getchar();}
	return p*f;
}
const int N=5e4+5;
#define A(x) (x+sum[x])
#define B(x) (x+sum[x]+1+L)
#define X(x) (B(x))
#define Y(x) (dp[x]+B(x)*B(x))
#define dx(x,y) (X(x)-X(y))
#define dy(x,y) (Y(x)-Y(y))
#define slope(x,y)(double(dy(x,y))/dx(x,y))
int n,L,sum[N],dp[N],q[N],qi=1,qn=1;
signed main(){
	n=in,L=in;
	for(int i=1;i<=n;i++)
		sum[i]=sum[i-1]+in;
	for(int i=1;i<=n;i++){
		while(qi<qn&&slope(q[qi+1],q[qi])<2*A(i))qi++;
		dp[i]=dp[q[qi]]+(A(i)-B(q[qi]))*(A(i)-B(q[qi]));
		while(qn>qi&&slope(q[qn],q[qn-1])>slope(i,q[qn-1]))qn--;
		q[++qn]=i;
	}
	cout<<dp[n];
	return 0;
}
題外話:

真的是一道極好的斜優入門題。