DP斜率优化

斜率优化

hdu3507

要输出N个正数字 a[N],输出的时候可以连续的输出,每连续输出一串,它的费用是 :这串数字和的平方加上一个常数 M。 求费用最小。

网课手推式子

f[i]=min{f[j]+sum[ij+1]2+M}

上述式子正确

时间复杂度为 O(N2),500000 直接起飞~

前方高能

我们对上述式子进行定义:

我们定义两个决策点j,k 并且 j>k, 那么我们要想使 dp 值从 j 转移的解比从 k 的更优的条件应该符合什么?

代入式子得:

f[i]+(s[i+1]s[j])2+M<f[k]+(s[i+1]s[k])2+M

移项得

f[j]+s[j]2]f[k]+s[k]2<2×s[i+1]×(s[j]s[k])

最终得

(f[j]+s[j]2)+(f[k]+s[k]2)s[j]s[k]<2×s[i+1]

dp[x]=f[x]+s[x]2,代换得

dp[j]dp[k]s[j]s[k]<2×s[i+1]

故当条件满足上述不等关系时,jk 转移更优,(j>k这里指的是位置更靠后)

我们将(s[k],dp[k]) 看做一个点的话,那么上述式子则表示的就是斜率公式(纵差比横差),故我们可以画出两点之间的斜率

那么对于我们所求的当前 f[t] ,中上述三个决策点的劣优就可以得到了 为:kj<i,前提是我们必须知道不等关系右边的大小

现在推到广义上,jk,i 变量之间不知道存在的位置大小关系,

那么就存在三种情况

第一种:

dp[j]dp[k]s[j]s[k]>dp[i]dp[j]s[i]s[j]>2×s[t+1]

优劣程度:i<j<k

第二种

dp[j]dp[k]s[j]s[k]>2×s[t+1]>dp[i]dp[j]s[i]s[j]

优劣程度:i>k>j

第三种

2×s[t+1]>dp[j]dp[k]s[j]s[k]>dp[i]dp[j]s[i]s[j]

优劣程度:i>j>k

故我们在这三种情况下得出不可能是最优的,那么它就可以不做决策点,即上凸图像不可做决策点

因此我们只需要维护下凸点即可,具体就是用队列进行维护

对于不同的斜率不等式,需要维护的凸点是不同的,小于就是维护下凸,大于维护上凸

问题是凸包中谁是更优呢?

自然就是斜率不大于当前斜率

边界是 s[t+1]

优劣程度:k>l>j>i

若大于,则前者优

若小于,则后者优,故 kl 更优

二分(在右侧不存在单调性)

实现方式:

找到不大于 s[t+1] 的最大位置,如果判断出 kj 优,那么往后的 s[t+2,+3,+4...] 都不会在用到 j,因此可以将 j 剔除,为什么?因为 s[i] 存在单调性,往后的值只会用到当前有用的决策点,并且优先选择更优的决策点,那么留着 j 干什么,所以提出

这个过程可以利用单调队列来维护,保证队列中一直是当前有用决策点的集合

对于当前不合法的就直接出队, 合法但不是已经有比他更优的也弹出队

int getup(int j, int k)
{
  return dp[j] + sum[j] * sum[j] - dp[k] - sum[k] *sum[k]
}//推导式中上部分
int getdown(int j, int k)
{
  return 2 * (sum[j] - sum[k])
}
int getdp(int i, int j)
{
  return dp[j] + (sum[i] - sum[j]) * (sum[i] - sum[j]) + m
}

head = tail = 1;
q[1] = 0;
for (int i = 1; i<= n; i++)
{
  while (head < tail && getup(q[head+1], q[head])) <= sum[i] * getdown(q[head+1], q[head]) head++;
//(合法与不合比较)  
  
  dp[i] = getdp(i, q[head]);
   
  while (head < tail && getup(i, q[tail]) * getdown(q[tail], q[tail-1])<=getup(q[tail], q[tail] - 1) * getdown(i, q[tail])) tail--;
//队首的下一个比队首更优,那么就将它弹出 
//合法中更优解比较,若斜率之比更小1,那么前者小于后者,即 i 优于 tail, 那么tail 在以后的比较将不会被用到,那么弹出  
  
  q[++tail] = i;
  //将可能是以后的更优解加入队列,为什么是可能?因为它可能也会被下一个所代替,不过还是需要让他入队,
  //我们不知道他的作用,即使无用在下一次的决策点判断中也会弹出 
}

引进概念

类型 : 优化状态转移

模式:

假定决策点之间的位置关系,

代入推公式

得到斜率公式,跑单调队列

"<" 维护下凸 ">" 维护上凸

BZOJ3156

f[i]=f[j]+t=j+1i(it)+a[i]=f[j]+i×(ij)t=j+1it+a[i]=f[j]+i×(ij)(sum[i]sum[j])+a[i]

推到的话我就省略了,为了简单好推,用 k=k+1,j=j+1

2×(f[k]f[j])+K×kJ×jk+KjJ<i

#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;

const int A = 1e7+10;
const int B = 1e6+10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline ll read() {
  char c = getchar();
  ll x = 0, f = 1;
  for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
  for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
  return x * f;
}

ll n,a[B],f[B],head,tail,q[B],ans,t;

double xie(ll k,ll j) 
{
    return ((2.0*(f[k]-f[j])+k*(k+1)-j*(j+1))/(2*k-2*j));
}

int main()
{
    n=read();
    for (ll i=1;i<=n;i++) a[i]=read();
    for (ll i=1;i<=n/2;i++) t=a[i],a[i]=a[n-i+1],a[n-i+1]=t;
    
    head=tail=1ll;
    q[1]=1ll;
    f[1]=a[1];
    ans=a[1]+1ll*n*(n-1)/2ll;
    
    for (ll i=2;i<=n;i++)
    {
        while (head<tail && xie(q[head+1],q[head])<i) head++;
        
        f[i]=f[q[head]]+1ll*(i-q[head])*(i-q[head]-1)/2+a[i];
        ans=min(ans,f[i]+1ll*(n-i+1)*(n-i)/2);
        
        while (head<tail && xie(q[tail],q[tail-1])>xie(i,q[tail])) tail--; 
        q[++tail]=i;
    }
    
    printf("%lld",ans);
} 

总结

斜率优化就是通式

dp[i]=max/min{f[j]+g[i]×h[j]}+a[i]

抽象的点 X 需要单调, Y 不需要

不等式右边的需要保证单调性,不单调就二分查找维护

本文作者:zxsoul

本文链接:https://www.cnblogs.com/zxsoul/p/14383808.html

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

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