其实关于数形结合的这种思想我一直不太明白
最近学了一下关于斜率优化方面的知识,才慢慢地理解了这种思想方法
使用条件
关于动态规划
如果方程形如:
就是在方程中有关于i的常数和关于j的常数的乘积。这时候使用斜率优化最恰当。
主要思想
数形结合!!
我们观察式子:
我们要使得决策点j优于决策点k
那就是
我们把关于j,k的放在不等式左边,然后把常数(已知的)都放右边
这样左边的式子就是一个斜率一样的东西
即,j点的坐标为(\(f[j]\),\(-c[j]\))
然后我们可以把整个式子化成一个一次函数的形式:
其中我们知道了j的坐标,所以:
所以我们发现了这是一个\(y=kx+b\)的形式
可以看成一条一次函数。
找决策点的任务是使得我们得出来的\(f[i]\)最小,所以,应使得上述解析式在j为一定值时的截距最小,只是我们可以想到维护一个下凸壳。
为什么要维护凸壳?
我们可以把每次查询的过程理解为一个一次函数,已经确定了斜率,现在我们在坐标系中有一堆散点,然后我们要把这个直线从下往上推上去,直到经过第一个决策点j为止,则在这时,直线的截距必定最小,这个j就是我们所说的最优决策点。
我们来看一下图:
如上图,我们在一堆散点中维护出一个下凸壳,在每次查询i时会有一个斜率,则这个斜率就可以在我们维护的下凸壳中找到一个最优决策点j,使得截距最小。
如何维护下凸壳?
维护一个下凸壳,我们可以使用一个队列(其实严格意义不能说是一个队列,因为它队首可以出,队尾可以进可以出)。
上面看起来很懵逼,我们来看一下每次要什么操作:
int h=1,t;
d[t=1]=0;
for (i=1;i<=n;++i){
while(h<t&&本次询问i的斜率>队首和队首后一个构成的直线的斜率) ++h;
f[i]=a[i]*f[d[h]]+b[i]*c[j]+d[i];
while(t>h&&即将放入队列中的i点和队尾的斜率<=队尾和其前一个点构成的斜率) --t;
d[++t]=i;
}
其中,第二个while的斜率比较我用了<=,我也不知道为什么,在做HDU3507的时候,交到oj上时,不加这个=就错了...
上面代码仅限维护下凸壳,如果是上凸壳的话,注意一下不等号的方向。
斜率不单调?
有两种奇怪的情况:
1.询问点i所对应的斜率不单调
这种情况比较普通,我们就不能像上面那样,直接比较队首了,因为...显然啊。
那么我们可以直接在这个单调的凸壳上面二分,这样也可以保证有一个n log的时间。
2.决策点j的横坐标不单调
这个的话,因为作者能力有限,并不会,只知道可以用平衡树或者线段树之类的维护。
有兴趣的可以了解一下李超树。
注意
比较斜率的时候可能会有严重的精度问题,所以最好把等式两边斜率的分子分母交叉相乘来判断。更加重要的是,因为不等式两边同时乘或除以一个负数,不等号的方向会改变,所以我们要保证分子分母都是非负数再乘过去,不然会出很大的问题。
例题
上面提到的HDU3507就是道好题,大家有兴趣可以去看看。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=5e5+10;
int n,m;
int c[maxn];
int f[maxn];
int sum[maxn];
int d[maxn*4];
int X(int x){
return sum[x];
}
int Y(int x){
return f[x]+sum[x]*sum[x];
}
int main(){
int i,j;
while(scanf("%d%d",&n,&m)!=EOF){
for (i=1;i<=n;++i)
scanf("%d",&c[i]),sum[i]=sum[i-1]+c[i];
memset(f,127,sizeof(f)),f[0]=0;
int h=1,t;
d[t=1]=0;
for (i=1;i<=n;++i){
while(h<t&&2*sum[i]*(X(d[h+1])-X(d[h]))>Y(d[h+1])-Y(d[h])) ++h;
f[i]=f[d[h]]+(sum[i]-sum[d[h]])*(sum[i]-sum[d[h]])+m;
while(t>h&&(Y(d[t])-Y(i))*(X(d[t-1])-X(d[t]))<=(X(d[t])-X(i))*(Y(d[t-1])-Y(d[t]))) --t;
d[++t]=i;
}
printf("%d\n",f[n]);
}
}
总结
我们来总结一下做这种题的一般步骤,加深理解:
1.首先列出dp方程。
2.然后像上面第一点一样先用j和k之间的决策优秀性来解出不等式,确定每个决策点的横纵坐标。
3.按照刚刚确定的横纵坐标来把原方程移项,拆成y=kx+b的形式(小技巧:观察,有关于i的常量和关于j的变量相乘的项,一般关于i的常量就是斜率)。
4.可以开始确定维护凸壳的形状,以及维护的方式和查询的方式。
5.切掉这道题。
6.AK这套比赛!
本人太菜,如有问题还请大佬多多海涵!