【题解】hdu3507 斜率优化

11 篇文章 0 订阅

题目链接

/*
假设sum[i]表示前i项和
dp[i]表示前i项需要花费的最少值
则dp[i]=min((sum[i]-sum[j])^2+m+dp[j]);//j=0~i-1
=>dp[i]=min(sum[i]*sum[i]+2*sum[i]*sum[j]+sum[j]*sum[j]+m+dp[j]);
由于存在sum[i]*sum[j]和i有关的这一项,所以不可能用单调队列来维护
2*sum[i]*sum[j]+sum[j]*sum[j]+m+dp[j]的最小值
这个时候怎么办??根据题意我们是要寻找到最优的j
所以假设k<j<i
有2*sum[i]*sum[j]+sum[j]*sum[j]+m+dp[j]<2*sum[i]*sum[k]+sum[k]*sum[k]+m+dp[k]
=>(sum[j]*sum[j]+dp[j]-(sum[k]*sum[k]+dp[k]))/(2*sum[j]-2*sum[k])<sum[i];
令
yj=sum[j]*sum[j]+dp[j]
yk=sum[k]*sum[k]+dp[k]
xj=2*sum[j];
xk=2*sum[k]
所以=>(yj-yk)/(xj-xk)<sum[i]则表示j比k更优 
是连接点j和点k的直线的斜率
相对于点i最优的是j,
则在0~j相对于i+1,i+2...n点j肯定是最优的,因为满足sum[i+x]>sum[i]>yj-yk)/(xj-xk)
则只需要继续判断j+1~i+x之间是否有点更优,有的话j就不用保存了,这个过程用单调队列实行
而对于点i应该添加到队列中
比较i与点q[tail-1],q[tail-1]与点q[tail-2]的斜率k1,k2,
若k1<=k2则将点q[tail-1]去除,因为后面肯定是先k1<sum[i+x] 
也就是点i肯定比q[tail-1]更优
然后重复比较直到k1>k2
*/
#include<cstdio>
const int N=5e5+10;
int n,m;
int dp[N],sum[N],q[N];
//dp[i]表示前i项需要花费的最少值
//sum[i]维护前缀和
//q维护单调队列
int GetY(int i,int j)//获取纵坐标 
{
	return sum[i]*sum[i]+dp[i]-(sum[j]*sum[j]+dp[j]);
}
int GetX(int i,int j)//获取横坐标 
{
	return 2*(sum[i]-sum[j]);
}
int main()
{
	//freopen("in.txt","r",stdin);
	int x;
	while(~scanf("%d%d",&n,&m))
	{
		int head=0,tail=0;//头指针尾指针
		q[tail++]=0;//这一步必须,因为可能前i个数全部作为一段才是最小值
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&x);
			sum[i]=sum[i-1]+x;//计算前缀和 
			while(head+1<tail&&GetY(q[head+1],q[head])<=GetX(q[head+1],q[head])*sum[i])++head;//更新最优的点
			dp[i]=(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]])+m+dp[q[head]];//计算dp[i]的最小值 
			while(head+1<tail&&GetY(i,q[tail-1])*GetX(q[tail-1],q[tail-2])<=GetY(q[tail-1],q[tail-2])*GetX(i,q[tail-1]))--tail;
			q[tail++]=i;
		} 
		printf("%d\n",dp[n]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>