【题解】P3195 [HNOI2008]玩具装箱TOY 斜率dp

给定N(50000),L(1e7),一个长为N的数列C(1e7),要将数列分成若干段(斜率dp全是分段?)。
每段的代价为 ( ( Σ C i ) + ( 段 长 − 1 ) − L ) 2 ((\Sigma C_i)+(段长-1)-L)^2 ((ΣCi)+(1)L)2,求最小的总代价。


d i d_i di表示把前 i i i个数分好段的最大价值, d 0 = 0 d_0=0 d0=0
d i = m i n { d j + ( s i − s j − 1 − L ) 2 } , 0 < = j < i d_i=min\{d_j+(s_i-s_j-1-L)^2\},0<=j<i di=min{dj+(sisj1L)2}0<=j<i,其中 s i s_i si C i + 1 C_i+1 Ci+1的前缀和;
整理得: d i = ( s i − 1 − L ) 2 + m i n { d j + s j 2 − 2 s j ( s i − 1 − L ) } d_i=(s_i-1-L)^2+min\{d_j+s_j^2-2s_j(s_i-1-L)\} di=(si1L)2+min{dj+sj22sj(si1L)}

j < k < i j<k<i j<k<i,那么 i i i k k k转移要比从 j j j转移更优等价于
( d k + s k 2 ) − ( d j + s j 2 ) 2 s k − 2 s j < s i − 1 − L \frac{(d_k+s_k^2)-(d_j+s_j^2)}{2s_k-2s_j}<s_i-1-L 2sk2sj(dk+sk2)(dj+sj2)<si1L

使用斜率优化 y i = d i + s i 2 , x i = 2 s i y_i=d_i+s_i^2,x_i=2s_i yi=di+si2xi=2si,目标斜率是 s i − 1 − L s_i-1-L si1L
维护下凸包,相邻斜率递增,目标斜率单调递增,双端队列。


因为L开成局部变量以及队列默认元素写成了1,debug了45分钟,我服了。

这题和前面的题基本一样。

/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 50016, MOD = 1000000007;

ll n, L, sum[M], dp[M];
int q[M], hd, tl;
inline ll subx(int j, int k)
{
	return 2*(sum[k]-sum[j]);
}
inline ll suby(int j, int k)
{
	ll yj = dp[j]+sum[j]*sum[j];
	ll yk = dp[k]+sum[k]*sum[k];
	return yk-yj;
}
inline ll cal(int i, int j)
{
	return dp[j] + (sum[i]-sum[j]-1-L)*(sum[i]-sum[j]-1-L);
}
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	n = read(), L = read();
	for(int i=1; i<=n; ++i)
		sum[i] = sum[i-1] + read() + 1;

	hd = tl = 0; q[tl++] = 0;
	for(int i=1; i<=n; ++i)
	{
		while(tl-hd>=2 && suby(q[hd], q[hd+1])<=subx(q[hd],q[hd+1])*(sum[i]-1-L)) ++hd;
		dp[i] = cal(i, q[hd]);
		while(tl-hd>=2 && 
			suby(q[tl-2],q[tl-1])*subx(q[tl-1],i)>=subx(q[tl-2],q[tl-1])*suby(q[tl-1],i)
		) --tl;
		q[tl++] = i;
	}
	cout << dp[n] << endl;

    return 0;
}


inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值