设A(x),B(x),C(x),D(x)为仅关于x的一元函数
单调队列DP
DP转移方程需要满足的条件:
dp[i]=A(i)+B(j)中的最小/大值 (i-k<=j<i,k为常数)
例子:
dp[i]=dp[j]+(i-j)*w // 选自hdu3401
A(i)=i*w
B(j)=dp[j]-j*w
分析:
维护B(j)的最大合法值进行转移即可
实现:
建立一层循环i=1~n
每次先用i-k更新单调队列队头位置,然后用单调队列中的最值点更新dp[i],最后将B(i)加入单调队列
struct dddl//单调队列 { int q[maxn]; int p[maxn]; int h,t; void clear(){h=1,t=0;} void eliminate(int minp){while(p[h]<minp&&h<=t) h++;} void insert(int pos,int val) { while(q[t]<=val&&h<=t) t--; q[++t]=val; p[t]=pos; } int top(){return q[h];} }que; int dp[maxn]; void dynamic_programming()//以取最大值为例 { que.clear(); dp[0]=0; que.insert(0,0);//预处理,具体函数值视情况而定 for(int i=1;i<=n;i++) { que.eliminate(i-k); dp[i]=que.top()+A(i); que.insert(i,B(i)); } }
斜率优化DP
DP转移方程需要满足的条件:
dp[i]=-A(i)*B(j)+C(i)+D(j) 中的最小值,j<i,A(x),B(x)为增函数
dp[i]=-A(i)*B(j)+C(i)+D(j) 中的最大值,j<i,A(x)为减函数,B(x)为增函数
例子:
dp[i]=dp[j]+(s[i]-s[j])^2 /*此处指平方*/ +M // 选自HDU3507
A(i)=s[i]
B(j)=s[j]*2
C(i)=s[i]*s[i]+M
D(j)=s[j]*s[j]+dp[j]
分析:
以取最小值为例
当从j>k,j转移到i比从k转移到i更优时有
-A(i)*B(j)+C(i)+D(j)<-A(i)*B(k)+C(i)+D(k)
变形得[D(j)-D(k)]/[B(j)-B(k)]<A(i)
设g(j,k)=[D(j)-D(k)]/[B(j)-B(k)]
可以证明,对于a<b<c,若g(c,b)<g(b,a),b永远不可能作为转移的最优决策,因为当g(c,b)<A(i),c比b优,当g(b,a)>g(c,b)>A(i),a比b优,那么b点可以删除
把D(j)当作y,B(j)当作x,那么g(j,k)就是连接j,k两点的线段斜率,最后维护出的队列为一个下凸包,如下图
当转移到i时我们选择前面的斜率小于A(i)后面斜率大于A(i)的点转移,顺便把队头定义到这个位置。
设找到点j,这样可以保证对于任意k>j有g(k,j)>A(i),j更优;对于任意k<j有g(j,k)<A(i),j还是更优
实现:
我们需要维护一个队列,使得其中元素两两之间的斜率递增
因为A(i)单增,所以可以定期删除队头元素,每次转移从队头取然后将新的点加入队尾并维护斜率单增
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; int n,m; long long dp[maxn]; int que[maxn]; long long ky(int a,int b) { return D(a)-D(b); } long long kx(int a,int b) { return B(a)-B(b); } void dynamic_programming()//以取最小值为例 { dp[0]=0;//预处理,具体函数值视情况而定 que[++t]=0; for(int i=1;i<=n;i++) { while(t>h&&ky(que[h+1],que[h])<=kx(que[h+1],que[h])*A[i]) h++; //一定要写成相乘形式,否则会引起除以0,精度低等一系列问题 dp[i]=A(i)*B(que[h])+C(i)+D(que[h]); while(t>h&&ky(i,que[t])*kx(que[t],que[t-1])<=ky(que[t],que[t-1])*kx(i,que[t])) t--; que[++t]=i; } }