功能
对于方程形如
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;
}