既然选择了远方,便只顾风雨兼行|

H_W_Y

园龄:1年11个月粉丝:28关注:15

2023-04-30 21:11阅读: 32评论: 0推荐: 0

斜率优化

斜率优化

dp回顾

对于所有的方程都需要枚举 j=[l,i1]

  1. dp[i]=max/min(dp[j]+a[i]) 维护出前缀的最值即可
  2. dp[i]=max/min(dp[j]+a[j]) 维护出前缀的最值即可
  3. dp[i]=max/min(dp[j]+a[j]+a[i]) 维护出前缀的最值即可
  4. dp[i]=max/min(dp[j]+a[i]b[j])
    特殊:a[i]b[j] 是否存在单调性,a,b 数组本身是单调的

斜率优化可优化的dp类型就是第四种

P3195 [HNOI2008]玩具装箱

题目描述

分析

dp[i] 表示到 i 为止的最小花费
对于 dp[i] 枚举所有的 1j<j
dp[i]=min(dp[j]+(C[j+1,i]+ij1L)2)
dp[i]=min(dp[j]+(sum[i]sum[j]+ij1L)2)

dp[i]=min(dp[j]+(sum[i]+isum[j]j1L)2)
F[i]=sum[i]+i
dp[i]=min(dp[j]+(F[i]F[j]1L)2)

这个方程就满足上面所说的第4类方程,就可以用到斜率优化

斜率优化

需要先把方程推导出斜率的公式 ΔyΔx

  1. 自己设置两个决策点 j1j2(j1<j2) 并且 j2 优于 j1
    代入这两个决策点到方程中,推导什么样条件满足 j2 优于 j1
    dp[j1]+(F[i]F[j1]1L)2>=dp[j2]+(F[i]F[j2]1L)2

  2. 拆开不等式,把不等式变成斜率的形式
    dp[j1]+F[i]22F[i](F[j1]+1+L)+(F[j1]+1+L)2
    >=dp[j2]+F[i]22F[i](F[j2]+1+L)+(F[j2]+1+L)2

    2F[i](F[j2]+1+L)2F[i](F[j1]+1+L)
    >=dp[j2]+(F[j2]+1+L)2(dp[j1]+(F[j1]+1+L)2)

    2F[i](F[j2]F[j1])>=dp[j2]+(F[j2]+1+L)2(dp[j1]+(F[j1]+1+L)2)

    2F[i]>=dp[j2]+(F[j2]+1+L)2(dp[j1]+(F[j1]+1+L)2)F[j2]F[j1]

    G[i]=(F[i]+1+L)2,代入
    2F[i]>=(dp[j2]+G[j2])(dp[j1]+G[j1])F[j2]F[j1]

  3. 显然我们可以把 F[j] 看做 x 横坐标,dp[j]+G[j] 看做 y 纵坐标,那么右边的式子也就变成了一个斜率 k=ΔyΔx,也就是说 k<=2F[i],决策点 j2 优于 j1

  4. 在决策点 j 依次向右移动的过程中,只有保证单调递增,所有的 j 才有可能成为最优决策点
    如果存在三个决策点 j1,j2,j3 出现 k(j1,j2)的斜率大于 k(j2,j3) 的斜率,那么 j2 必然不可能成为最优决策点
    image
    也就是说,
    j3在黄色点时,c>a 此时无论2F[i]是多大,j1,j2,j3都有可能成为最有决策点
    而当j3在蓝色点位置时,则a>bj2是不可能成为最有决策点的,因为无论什么情况,j3都会比j2优,所以直接可以把j2删除,这样所得到的序列就是单调的了

  5. 寻找答案,只需要二分查找维护出来的斜率队列,找到最大的那个满足条件的斜率,这个斜率所对应的右端点即为最优决策点 j
    当然,这里也可以直接用单调队列来维护决策点队列
    如果 i 和队首两个点的斜率 <2F[i] 则删除队首
    对于队尾两个点的斜率大于当前 i 和队尾的斜率,则删除队尾

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int maxn=5e4+10;
int n,head,tail;
ll a[maxn],sum[maxn],F[maxn],L,G[maxn],dp[maxn];
int dq[maxn];

double K(int j1,int j2){
  return (double)(dp[j1]+G[j1]-dp[j2]-G[j2])/(F[j1]-F[j2]);
}

int main(){
  /*2023.4.30 hewanying P3195 [HNOI2008]玩具装箱 斜率优化dp*/ 
  scanf("%d%lld",&n,&L);
  for(int i=1;i<=n;i++){
  	scanf("%lld",&a[i]);
  	sum[i]=sum[i-1]+a[i];
  	F[i]=sum[i]+i;
  	G[i]=(F[i]+1+L)*(F[i]+1+L);
  }
  G[0]=(L+1)*(L+1); //把G[0]当作原点,可以不用判断队伍中只有一个元素的情况
  for(int i=1;i<=n;i++){
  	while(head<tail&&K(dq[head],dq[head+1])<2*F[i]) head++;
  	dp[i]=dp[dq[head]]+(F[i]-F[dq[head]]-1-L)*(F[i]-F[dq[head]]-1-L);
  	while(head<tail&&K(dq[tail-1],dq[tail])>K(dq[tail],i)) tail--;
  	dq[++tail]=i;
  }
  printf("%lld\n",dp[n]);
  return 0;
}

总结

  1. 斜率优化的题目一般代码都比较短,n2的dp都比较好推,难就难在推公式的过程
  2. 在dp中可以常常设一些函数来表示固定的值,从而可以简化dp的式子,防止出现错误,如例题中的F[i],G[i]
  3. 注意初始化和是否要使用long long

本文作者:H_W_Y

本文链接:https://www.cnblogs.com/H-W-Y/p/17365781.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   H_W_Y  阅读(32)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起