【HDU 3507】Print Article

传送门


Problem

给出一个有 n n n 个自然数的序列 { a n } \{a_n\} {an},给出一个常数 m m m

现在要把这个序列分成若干段,每段的代价是 ( ∑ a i ) 2 + m (\sum a_i)^2+m (ai)2+m,求最小代价。

数据范围: 0 ≤ n ≤ 5 × 1 0 5 0 ≤ n ≤ 5\times10^5 0n5×105 0 ≤ m ≤ 1000 0 ≤ m ≤ 1000 0m1000


Solution

这是一道斜率优化 d p dp dp的板子题啦。

我们设 f ( i ) f(i) f(i) 表示处理到第 i i i 个数且从 i i i 切开的最小代价,显然有如下转移( S ( i ) S(i) S(i) 为前缀和):

f ( i ) = min ⁡ j = 1 i − 1 { f ( j ) + ( S ( i ) − S ( j ) ) 2 + a i } f(i)=\min_{j=1}^{i-1}\{f(j)+(S(i)-S(j))^2+a_i\} f(i)=j=1mini1{f(j)+(S(i)S(j))2+ai}

但是转移是 n 2 n^2 n2 的,不能过 1 e 5 1e5 1e5 的数据,要想办法优化。

我们考虑对于 j , k ( k &lt; j &lt; i ) j,k(k&lt;j&lt;i) j,k(k<j<i),满足什么条件时用 j j j 转移比用 k k k 转移更优。即:

f ( j ) + ( S ( i ) − S ( j ) ) 2 + a i &lt; f ( k ) + ( S ( i ) − S ( k ) ) 2 + a i f(j)+(S(i)-S(j))^2+a_i&lt;f(k)+(S(i)-S(k))^2+a_i f(j)+(S(i)S(j))2+ai<f(k)+(S(i)S(k))2+ai

化简后得到:

f ( j ) − f ( k ) + S ( j ) 2 − S ( k ) 2 &lt; 2 S ( i ) ( S ( j ) − S ( k ) ) f(j)-f(k)+S(j)^2-S(k)^2&lt;2S(i)(S(j)-S(k)) f(j)f(k)+S(j)2S(k)2<2S(i)(S(j)S(k))

又由于 j &gt; k j&gt;k j>k,且 S ( i ) S(i) S(i) 是单调不减的,所以有:

f ( j ) + S ( j ) 2 − ( f ( k ) + S ( k ) 2 ) S ( j ) − S ( k ) &lt; 2 S ( i ) \frac{f(j)+S(j)^2-(f(k)+S(k)^2)}{S(j)-S(k)}&lt;2S(i) S(j)S(k)f(j)+S(j)2(f(k)+S(k)2)<2S(i)

发现左边的那个式子像是斜率的计算式( k = y 2 − y 1 x 2 − x 1 k=\frac{y_2-y_1}{x_2-x_1} k=x2x1y2y1)。

于是把每个决策点的坐标写出来 ( S ( i ) , f ( i ) + S ( i ) 2 ) (S(i),f(i)+S(i)^2) (S(i),f(i)+S(i)2),在纸上推一推,不难发现我们要找的决策点在下凸壳上。

又由于 2 S ( i ) 2S(i) 2S(i) 是单调不减的,我们可以用单调队列维护这个下凸壳。

时间复杂度 O ( n ) O(n) O(n)


Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500005
using namespace std;
int n,m,x,f[N],Q[N],S[N];
int Squ(int x)  {return x*x;}
int X(int x)  {return S[x];}
int Y(int x)  {return f[x]+Squ(S[x]);}
//double slope(int x,int y)  {return 1.0*(f[y]-f[x]+Squ(S[y])-Squ(S[x]))/(1.0*S[y]-S[x]);}
int main(){
	while(~scanf("%d%d",&n,&m)){
		memset(f,0,sizeof(f));
		for(int i=1;i<=n;++i)  scanf("%d",&x),S[i]=S[i-1]+x;
		int l=0,r=0;
		for(int i=1;i<=n;++i){
			//while(l<r&&slope(Q[l],Q[l+1])<=2*S[i])  l++;
			while(l<r&&Y(Q[l+1])-Y(Q[l])<=(X(Q[l+1])-X(Q[l]))*2*S[i])  l++;
			f[i]=f[Q[l]]+Squ(S[i]-S[Q[l]])+m;
			//while(l<r&&slope(Q[r-1],Q[r])>=slope(i,Q[r]))  r--;
			while(l<r&&(Y(Q[r])-Y(Q[r-1]))*(X(i)-X(Q[r]))>=(Y(i)-Y(Q[r]))*(X(Q[r])-X(Q[r-1])))  r--;
			Q[++r]=i;
		}
		printf("%d\n",f[n]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值