动态规划——斜率优化

功能

对于方程形如

F [ i ] = F [ j ] + delta ( i, j )

的方程进行优化,使得时间复杂度由 O ( n ^ 2 ) 将为 O ( n )。其中 delta ( i, j )与 i, j 有关


例:HDU 3507


题目大意

给出一个数列,每个数为 Ci ( Ci > 0 ),欲将它们分为多组。又知,每组有一个 cost 为Sigma ( Ci ) * Sigma ( Ci ) + M。其中,M为常数,Ci为该组中的数的值。
求最小的 sigma( cost )


分析

很容易知道dp方程为
F [ i ] = F [ j ] + ( sum [ i ] - sum [ j ] ) * ( sum [ i ] - sum [ j ] ) + M
时间复杂度为 O ( n ^ 2 )


斜率优化

时间复杂度 O ( n )
假设 k < j < i,且 F [ k ] > F [ j ],即 j 比 k 优
那么
F [ k ] + ( sum [ i ] - sum [ j ] ) * ( sum [ i ] - sum [ j ] ) + M> F [ j ] + ( sum [ i ] - sum [ k ] ) * ( sum [ i ] - sum [ k ] ) + M

=> [ ( F [ j ] + sum [ j ] * sum [ j ] ) - (F [ i ] + sum [ i ] * sum [ i ] ) ]  /  2 * ( sum [ j ] - sum [ k ] ) < sum [ i ]  ( 1 )

Step1

设 y[i] = F [ i ] + sum [ i ] * sum [ i ] , x [ i ] = 2 * sum [ i ]
设 g [ i, j ] =  ( 1 ) 式中小于号左侧的式子 = ( y [ j ] - y [ k ] ) / ( x [ j ] - x [ k ] )
可以发现,g [ i, j ] 代表斜率
并且,g [ j, k ]  <= sum [ i ] 代表在 F [ i ] 中,j 与 k 一样优,或者比 k 优

Step2

若 g [ i, j ] <= g [ j, k ],说明在 j 点不比 i 或 k 点优。
证明:
当 g [ j, k ] < sum [ X ],说明对于 X,j 比 k 更优,但是由于 g [ i, j ] <= g [ j, k ] < sum [ X ],所以 i 跟 j 一样优,或者比 j 更优。
当 g [ j, k ] = sum [ X ],说明对于 X, j 和 k 一样优,但是由于 g [ i, j ]  <= g [ j, k ] < sum [ X ],所以 j 不比其他点更优

Step3

将点 i 依次加入队列中。

每次计算dp [ i ] 时,从队头开始一个个查询,假设当前队列中的点和下一点分别为 j、j + 1,如果 g [ j + 1, j ] <= sum [ i ],那么点 j 跳出队列,即队首++。
因为根据Step1可知,对于当前点 i 来说,j + 1 比 j 优,而对于之后的 i + 1 ~ n 点来说,由于sum [ i ] 的值递增,所以 j + 1 也是比 j 优的。

每次加入点 i 时,从队尾开始一个个判断,假设当前队列中的点和前一点分别为 j - 1、j - 2,如果 g [ i, j - 1 ] <= g[j - 1, j - 2],那么 j - 1 点跳出队列,即队尾--。
因为根据Step2可知,对于 i 之后的任意点,如果 g [ i, j - 1 ] <= g [ j - 1, j - 2 ],那么 j - 1 不比任何点优

程序

#include <iostream>
#include <cstdio>
#include <cstdlib>

using namespace std;

const int Max_N = 500010;

int n, m;
int sum[Max_N];

void Init()
{
	sum[0] = 0;
	for(int i = 1; i <= n; i ++)
	{
		int x;
		scanf("%d", &x);
		sum[i] = sum[i - 1] + x;
	}
}

int dp[Max_N];
int q[Max_N], q_l, q_r;

int Cal_Dp(int x, int y)
{
	return dp[y] + (sum[x] - sum[y]) * (sum[x] - sum[y]) + m;
}

int Cal_Dy(int x, int y)
{
	return dp[x] + sum[x] * sum[x] - (dp[y] + sum[y] * sum[y]);
}

int Cal_Dx(int x, int y)
{
	return 2 * (sum[x] - sum[y]);
}

void Solve()
{
	dp[0] = 0;
	q_l = q_r = 0;
	q[q_r ++] = 0;
	for(int i = 1; i <= n; i ++)
	{
		while(q_l + 1 < q_r && Cal_Dy(q[q_l + 1], q[q_l]) <= Cal_Dx(q[q_l + 1], q[q_l]) * sum[i])//< and <= don't bother
			q_l ++;
		dp[i] = Cal_Dp(i, q[q_l]);
		while(q_l + 1 < q_r && Cal_Dy(i, q[q_r - 1]) * Cal_Dx(q[q_r - 1], q[q_r - 2]) <= Cal_Dy(q[q_r - 1], q[q_r - 2]) * Cal_Dx(i, q[q_r - 1]))//why
			q_r --;
		q[q_r ++] = i;
	}
	printf("%d\n", dp[n]);
}

int main()
{
	while(scanf("%d%d", &n, &m) == 2)
	{
		Init();
		Solve();
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值