啊啊,斜率优化啊。
推荐一篇很好的博客 http://www.cnblogs.com/MashiroSky/p/6009685.html(感觉说“转载”有点怪好像说推荐的博客是转载的。。总之就表达一下对那位作者作品权的尊重以及对那位大佬的尊敬与感激。。。)
对象:形如dp[i]=max/min(dp[j]+a[i]*b[j]+c[i])等,a,b为单调函数,转移主要发生在一维,在转移式中有关于i和j的函数相乘的情况时,无法进单调队列优化。那么此时我们可以考虑进行斜率优化。
注意,并不是所有的上类dp都可以进行斜率优化,这也是为什么在做斜率优化前需要证明 决策单调性 问题。
步骤:1.证明这个dp具有决策单调性,个人理解就是,对于两种决策方案j,k,k>j,若在转移i时k方案优于j方案,那么对于i+1而言,k也一定优于j。
证明方法:
这里用上面的dp作例
首先根据i时k方案优于j,可以列出第一个不等式:
dp[j]+a[i]*b[j]+c[i]>=dp[k]+a[i]*b[k]+c[i](若是max就取<=,这里用min举例) ——【1】
设a[i+1]=a[i]+v,显然当a递增时v>0,当a递减时v<0;
那么就有
dp[j]+(a[i]+v)*b[j]+c[i]>=dp[k]+(a[i]+v)*b[k]+c[i]
将【1】代入,可得
v*b[j]>=v*b[k] ——【2】
在k>j的条件下,判断它和b所具有的单调性相不相同即可。
2.接下来就是求斜率表达式了
对上式,我们不妨假设a是递减的,b是递增的(于【2】而言明显合法)
那么我们可以维护一个单调队列来存决策位置(指针),每次取直接取dp[i]=dp[q首]+a[i]*b[q首]+c[i],也就是说最好决策会在哪已经知道了,是不是很妙啊。
附一张图来看一下具体维护细节吧
而上述操作的实质则是在维护一个凸壳
嗯,这里就讲的差不多了。。。
但还是要强调,上面的式子终归是个模型。一个转移方程能否使用斜率优化的本质判断点在于它的二次项部分两个函数的增减性,系数正负以及是max还是min,不过那是数学问题了,一般情况下只要用上述证明方法自己就问题自己证明一下就好了。
后面附上小题三道
bzoj1010
#include<iostream>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<stdio.h>
using namespace std;
const int N=50050;
long long le[N],f[N];
long long dp[N],sum[N];
int q[N];
int n,l;
double slope(int j,int k)
{
return (f[k]*f[k]+2*f[k]*(l+1)+dp[k]-f[j]*f[j]-2*f[j]*(l+1)-dp[j])/(2*(f[k]-f[j]));
}
int main()
{
scanf("%d%d",&n,&l);
for(int i=1;i<=n;i++)
scanf("%d",&le[i]),sum[i]=sum[i-1]+le[i],f[i]=sum[i]+i;
int h=0,t=0;
for(int i=1;i<=n;i++)
{
while(h>t&&slope(q[t],q[t+1])<=f[i]) t++;
dp[i]=dp[q[t]]+(f[i]-f[q[t]]-l-1)*(f[i]-f[q[t]]-l-1);
while(h>t&&slope(q[h-1],q[h])>slope(q[h],i)) h--;
q[++h]=i;
}
printf("%lld\n",dp[n]);
}
bzoj1096
#include<iostream>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<stdio.h>
using namespace std;
const int N=1000500;
int n;
int q[N];
long long x[N],p[N],c[N];
long long sum[N],f[N],dp[N];
double slope(int j,int k)
{
return (double)(dp[k]+f[k]-dp[j]-f[j])/(double)(sum[k]-sum[j]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld%lld",&x[i],&p[i],&c[i]);
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+p[i],f[i]=f[i-1]+p[i]*x[i];
int h=0,t=0;
for(int i=1;i<=n;i++)
{
while(h>t&&slope(q[t],q[t+1])<=x[i]) t++;
dp[i]=dp[q[t]]+(sum[i]-sum[q[t]])*x[i]+(f[q[t]]-f[i])+c[i];
while(h>t&&slope(q[h-1],q[h])>=slope(q[h],i)) h--;
q[++h]=i;
}
printf("%lld\n",dp[n]);
}
bzoj1911
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
const int N=1000500;
int a,b,c,n;
long long x[N],f[N];
long long dp[N];
int q[N];
double slope(int j,int k)
{
return (double)(dp[k]+a*f[k]*f[k]-b*f[k]-dp[j]-a*f[j]*f[j]+b*f[j])/(double)(2*a*(f[k]-f[j]));
}
int main()
{
scanf("%d",&n);
scanf("%d%d%d",&a,&b,&c);
for(int i=1;i<=n;i++)
scanf("%lld",&x[i]),f[i]=f[i-1]+x[i];
int t=0,h=0;
for(int i=1;i<=n;i++)
{
while(h>t&&slope(q[t],q[t+1])<=f[i]) t++;
dp[i]=dp[q[t]]+a*(f[i]-f[q[t]])*(f[i]-f[q[t]])+b*(f[i]-f[q[t]])+c;
while(h>t&&slope(q[h-1],q[h])>=slope(q[h],i)) h--;
q[++h]=i;
}
printf("%lld\n",dp[n]);
}