斜率优化DP做为1种较难理解的DP,在ACM中也有重要的作用。
现有的论文或博客讲斜率优化DP要么过于抽象,要么推倒过于繁琐,
这里用高中学的线性规划的知识来理解斜率优化DP,且思考方法具有一般性,
可用于解决各种斜率优化问题。
以hdu3507http://acm.hdu.edu.cn/showproblem.php?pid=3507为例题。
我们很容易能推出方程F【i】= min{F【j】+(Si-Sj)^2 + M }
这是1个1D/1D的方程,即状态O(N),转移O(N),直接求效率为O(N^2)。
本题N为500000,O(N^2)显然不行!
这里,用斜率优化的方法,将转移的O(N)优化为均摊复杂度O(1),总复杂度O(N)的效率解决这个问题。
首先,将方程展开,并将带有j的项整理出来:
F【i】= min{F【j】+Sj^2-2*Si*Sj+Si^2 + M }
下一步,是斜率优化的关键,即找出一个直线方程。
令K=2*Si,X=Sj,Y=F【j】+Sj^2,常数C=Si^2 + M
方程变为:
F【i】= min{Y-KX}+C
求某个F【i】,就是从前面的所有点(X,Y)的状态推来,我们开始描点。
对某个点(X,Y),它是由F【t】这个状态推来,我们称之为决策点t。
首先分析点随j的增加的趋势:j增加,可以推出X,Y都增加。
这样,我们可以把点描出来。
根据线性规划的知识,求min{Y-KX}+C,就等价于过点(X,Y)斜率为2Si的直线与y轴交点纵坐标的最小值!
下面开始优化的过程:
考虑3个决策点i,j,k(i < j < k),若Kji > Kkj, 那么我们求解的直线无论怎么变化,j点都不可能成为最优的决策点!
只要大家拿一个尺子,用线性规划的知识动手实践一下,就会发现上面这条规律。
我们在加入新的决策点的时候,就用单调队列维护相邻的决策点的斜率递增!
也就是维护决策点的下凸性!
在求当前状态时,从队首开始,把所有相邻决策点斜率小于2Si的点删除后的第一个点,就是最小的纵坐标的值。这里,把点描出来,拿尺子一比划就出来了。
对于一般性问题,通过分析点的分布,斜率的正负等,可以推出要维护的是上凸性还是下凸性以及如何求最优解。
/*
* p3507.cpp
*
* Created on: 2013-5-30
* Author: zy
*/
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const long long INF=0xfffffffffff;
const int maxn=500010;
long long f[maxn],s[maxn];
int a[maxn],m,n,l,r,q[maxn];
long long min(long long x,long long y)
{
return x<y?x:y;
}
double slope(int j,int k)
{
if(s[j]==s[k])
{
if(f[j]>=f[k])return INF;
else return -INF;
}
return (f[j]-f[k]+s[j]*s[j]-s[k]*s[k])/((double)s[j]-s[k]);
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(f,0,sizeof(f));
memset(q,0,sizeof(q));
s[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
s[i]=s[i-1]+a[i];
}
l=r=1;q[1]=0;
for(int i=1;i<=n;i++)
{
while(l<r&&slope(q[l],q[l+1])<2*s[i])l++;
f[i]=f[q[l]]+(s[i]-s[q[l]])*(s[i]-s[q[l]])+m;
while(l<r&&slope(q[r-1],q[r])>slope(q[r],i))r--;
r++;q[r]=i;
}
cout<<f[n]<<endl;
}
return 0;
}