决策单调性优化 DP

前言

本文将介绍决策单调性优化 DP 的相关内容。持续更新修正,如有差错请指出。

1.四边形不等式优化 DP

1.1 四边形不等式与决策单调性

  • 四边形不等式:如果对于任意的 abcd 均成立

w(a,d)+w(b,c)w(a,c)+w(b,d)

则称代价函数 w 满足四边形不等式。观察上述形式,即包含劣于相交,注意这是当我们要求代价函数 w 最小时四边形不等式的符号,如果我们要求 w 最大,相当于对其取相反数,那么相应的,此时的四边形不等式需要变号
四边形不等式优化利用的是状态转移方程中的决策单调性,通常用于解决一系列的最优化问题。
在解决动态规划相关问题的时候,通常会遇到以下这种形式

fi=minj<i{fj+w(j,i)}

其中 min 也可能是 max。一般情形下,这类问题解决的时间复杂度为 O(n2),如果 f 具有决策单调性,那么就可以将时间复杂度优化至 O(nlogn) 甚至 O(n)

  • 决策单调性:设 pi 表示 fi 取到最小值时 j 的值(如果有多个 j 满足则取最小),即 fi 的最优决策点。当代价函数 w 满足四边形不等式时,pi[1,n] 上单调不降,f 具有决策单调性。则我们有

i[1,n],j[0,pi),fpi+w(pi,i)fj+w(j,i)

要证明这一点,可以使用反证法。假设对于 fi,fj(i<j),其最优决策点 pj<pi,此时 pj<pi<i<j,据四边形不等式有 w(pj,j)+w(pi,i)w(pj,i)+w(pi,j)但是根据决策点的最优化条件又有 w(pi,i)w(pj,i),w(pj,j)w(pi,i),即 w(pj,j)+w(pi,i)w(pj,i)+w(pi,j)与四边形不等式矛盾。
由此得证。

对于 fi,其具有最小/最大最优决策点,将上述对 pi 的定义更换为取最大后,关于原 pi 的所有结论都是同样成立的,最大最优决策点同样具有单调不降的性质。注意可能存在 i<i,但是 i 的最大最优决策点小于 i 的最小最优决策点,故一般题目当中我们都默认只取最小(大)最优决策点来转移。

1.2 解题套路

通常我们先写出 fi 的转移式子,大多数情况下,通常使用

w(j,i+1)+w(j+1,i)w(j,i)+w(j+1,i+1)

来检验代价函数是否满足四边形不等式。

然后对于一个决策,取它作为最优决策点的 fi 所组成的是一个区间。对于决策 pi<pi,则这两种决策能成为最优决策的区间 [lpi,rpi],[lpi,rpi],有 rpi<lpi

我们写一个二分函数 check(j,i) 计算出第一个以 j 作为最优决策不如以 i 作为最优决策优秀的点,那么可以使用单调队列来维护最优决策点,并进行 DP 转移了。

2.斜率优化 DP

给出例题。

  • P3195 玩具装箱

对于 [l,r] ,其代价为 (rl+i=lrciL)2

首先对 ci 做前缀和。考虑暴力,对于每个 i 去枚举 j,则有

dpi=min{dpj+(ij1+cicjL)2}

rep(i,1,n) {
    dp[i] = inff;
    rep1(j,i - 1,0)
        chmin(dp[i],dp[j] + (i - j - 1 + c[i] - c[j] - L) * (i - j - 1 + c[i] - c[j] - L));
}

现在进行优化,把上述式子变形,把 1 放入 L 中,i,j 分别放入 ci,cj 中,有 (cicjL)2,把式子里的“常量”提出来展开,变为

dpi(ciL)2=dpj2×(ciL)×cj+cj2

接下来考虑进行斜率优化,对于一个一次函数 y=kx+b,通常推式子时有以下操作:

  1. 把要求最小值的式子作为截距,即 b=dpi(ciL)2
  2. 把另一边的式子变为 ykx 的形式,其中 y 只与 j 有关,kx 同时与 i,j 有关

y=dpj+cj2k=2(ciL)x=cjb=dpi(ciL)2

显然,bi 取到最小值的点在这个下凸壳上,因为这个斜率是单调的,可以考虑用单调队列来维护,此时 slope(qi1,qi)<slope(qi,qi+1)

那么当 slope(qi1,qi)k<slope(qi,qi+1)bqi 上取得最小值。

代码就很好写了。

il db slope(int i,int j) {
    return (db)(y[i] - y[j]) * 1.0 / (db)(x[i] - x[j]);
}

il void solve() {
    //------------code------------
    read(n,L); 
    ++ L;
    rep(i,1,n) read(c[i]),c[i] += c[i - 1];
    rep(i,1,n) c[i] += i;
    rep(i,1,n) {
        int k = 2ll * (c[i] - L);
        while (hh <= tt && slope(q[hh - 1],q[hh]) <= k * 1.0) ++ hh;
        dp[i] = y[q[hh - 1]] - k * x[q[hh - 1]] + (c[i] - L) * (c[i] - L);
        x[i] = c[i],y[i] = dp[i] + c[i] * c[i];
        while (hh <= tt && slope(q[tt - 1],q[tt]) >= slope(q[tt],i)) -- tt;
        q[++ tt] = i;
    }
    write(dp[n],'\n');
    return ;
}
posted @   songszh  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示