「Note」DP 方向 - DP 优化

1. 单调队列优化 DP

1.1. 简介

当一个选手比你小还比你强,你就打不过他了。这是对单调队列简单形象的概括。

单调队列在转移的过程中不断排除不可能成为决策点的元素,使每次转移寻找决策点的时间复杂度降为 \(O(1)\)。一般地,可被单调队列优化的转移式可被写为如下形式:

\[F_i=\max_{l_i\le j<i}\{F_j+A_j+B_i\} \]

\[(F_i=\min_{l_i\le j<i}\{F_j+A_j+B_i\}) \]

其中需要满足 \(l_i\) 单调不降\(A_i\) 是只与 \(i\) 有关的变量,\(B_j\) 是只与 \(j\) 有关的变量。

不难发现单调队列要维护的是 \(F_i+A_i\),每次 \(j1\) 入队时,若满足 \(F_{j1}+A_{j1}>F_{j0}+A_{j0}\) 则将 \(j0\) 弹出,直到不满足条件或者队列为空。

此时取队头元素,它一定满足在范围内最优,将其作为决策点即可。特殊地,有时需要特判队列为空的情况。

1.2. 常见技巧

大部分情况下,转移方程并不显著,因为在其中的贡献往往与 \(i,j\) 都有关,此时尝试将贡献转化为 \(A_j+B_i\) 的形式,然后在单调队列中维护 \(F_i+A_i\) 即可。

1.3. 例题

\(\color{limegreen}{P1776}\)(单调队列优化多重背包)

\(v_i\) \(w_i\) \(c_i\) \(F_{i,j}\)
物品 \(i\) 的体积 物品 \(i\) 的价值 物品 \(i\) 的个数 \(i\) 个物品放入总体积为 \(j\) 的物品的最大价值

显著地,有以下转移方程:

\[F_{i,j}=\max_{0\le k\le c_i\ \land\ k\times v_i\le j}\{F_{i-1,j-k\times v_i}+k\times w_i\} \]

\(j'=j-k\times v_i\),在保证 \(i\) 不变的情况下,分别考虑每个 \(j'\)\(j\) 的贡献,此时有 \(k=\frac{j-j'}{v_i}\)。此时 \(j,j'\)\(\bmod v_i\) 的意义下同余,设 \(j=t\times v_i+r\) 其中 \(0\le r<v_i\),考虑将贡献拆分有 \(j'=j-k\times v_i,k=\left\lfloor\frac{j}{v_i}\right\rfloor-\left\lfloor\frac{j'}{v_i}\right\rfloor=t-\left\lfloor\frac{j'}{v_i}\right\rfloor\),将原方程改写:

\[F_{i,j}=\max_{0\le k\le \min\{c_i,t\}}\{F_{i-1,j-k\times v_i}-\left\lfloor\frac{j'}{v_i}\right\rfloor\times w_i\}+t\times w_i \]

不难想到在枚举 \(r\) 的基础上,枚举 \(j\) 进行单调队列优化。

$\text{Code}$:
#define LL long long
#define UN unsigned
#include<bits/stdc++.h>
using namespace std;
//--------------------//
const int N=1e5+1;

int n,V;
int v[N],w[N],c[N];

int ans,f[N];

int head,tail;
int q[N][2];
//--------------------//
int main()
{
    scanf("%d%d",&n,&V);
    for(int i=1;i<=n;i++)
        scanf("%d%d%d",&w[i],&v[i],&c[i]);
    for(int i=1;i<=n;i++)
        for(int r=0;r<v[i];r++)
        {
            head=tail=1;
            q[1][0]=r,q[1][1]=f[r];
            for(int tem,t,j=r+v[i];j<=V;j+=v[i])
            {
                tem=f[j]-j/v[i]*w[i],t=j/v[i];
                while(head<=tail&&q[head][0]<j-c[i]*v[i])
                    head++;
                f[j]=max(f[j],q[head][1]+t*w[i]);
                while(head<=tail&&q[tail][1]<tem)
                    tail--;
                q[++tail][0]=j,q[tail][1]=tem;
            }
        }
    for(int i=1;i<=V;i++)
        ans=max(ans,f[i]);
    printf("%d",ans);
    return 0;
}

\(\color{royalblue}{P3089}\)

\(x_i\) \(p_i\) \(F_{i,j}\) \(now_i\)
目标点 \(i\) 的坐标 目标点 \(i\) 的分数 跳至目标点 \(i\),上一个目标点是 \(j\) ,所能获得的最大分数 当前单调队列还未加入的最近位置

先考虑单方向的做法,显著地,按照 \(x_i\) 排序后有以下转移方程:

\[F_{i,j}=\max_{x_i-x_j\ge x_j-x_k}\{F_{j,k}\}+p_i \]

我们发现当 \(i\) 固定时并不能直接用单调队列优化,因为维护的 \(\max\) 中存在两个不固定值 \(j,k\)。考虑固定 \(j\),当 \(i\) 上升时,\(k\) 的范围单调不减,此时可以用单调队列维护。

对于每一个 \(j\) 维护一个单调队列,用 \(now_j\) 记录还未进队的最近位置,每次枚举 \(i\) 时,将符合条件的 \(k\) 入队,并更新 \(now_j\)

至于向另一方向跳的情况,将数轴左右翻转再做一遍 DP 即可。

\(\color{royalblue}{P4544}\)

\(ka\) \(e\) \(x_i\) \(c_i\) \(v_i\) \(f_{i,j}\)
需要饲料总数 中点坐标 商店 \(i\) 坐标 商店 \(i\) 库存 商店 \(i\) 价格 走到商店 \(i\) 经过交易后得到 \(j\) 个饲料的最小消费

先按照 \(x_i\) 排序,显著地,转移方程:

\[F_{i,j}=\min_{j-c_i\le k<j}\{f_{i-1,k}+(x_i-x_{i-1})\times k^2+(j-k)\times v_i\} \]

将其转化:

\[F_{i,j}=\min_{j-c_i\le k<j}\{f_{i-1,k}+(x_i-x_{i-1})\times k^2+k\times v_i\}+j\times v_i \]

显著的单调队列。其中需要注意的是初值的设置以及转移的始末位置。

x. 前置知识 决策单调性

x.1. 简介

决策单调性是一个优秀的性质,对于具有决策单调性的动态规划可以采用很多方法进行优化。

通常地,决策单调性体现在 1D 维度上。当 \(j<j'<i<i'\) 时,若 \(F_i\)\(F_{j'}\) 转移过来,那么 \(F_i'\) 不可能从 \(F_j\) 转移过来,只可能从 \(F_j'\) 或之后的状态转移。这种性质可称之为决策单调性。

x.2. 四边形不等式

四边形不等式如下,在 \(a<b<c<d\) 的情况下 \(w(a,d)+w(b,c)>w(a,c)+w(b,d)\)

\(F_i\)\(F_j+w(i,j)\) 转移来,若满足四边形不等式则称其具有决策单调性。

证明略,待施工。

2. 斜率优化

2.1. 简介

当一个最优化 DP 的转移方程形如:

\[F_i=\min\{F_j+A_i+B_j+a_i\times b_j\} \]

\[(F_i=\max\{F_j+A_i+B_j+a_i\times b_j\}) \]

即其中有一些只关于 \(i,j\) 其中一个的项和一个关于 \(i,j\) 两个变量的项,可以考虑用斜率优化来解决。

先对原式进行化简。设 \(F_i\)\(F_j\) 转移过来,有 \(F_i=F_j+A_i+B_j+a_i\times b_j\),移项得:

\[F_j+B_j=-a_i\times b_j+(F_i - A_i) \]

\(y=F_j+B_j,x=b_j,k=a_i,b=A_i-F_i\),当 \(i\) 固定时,我们得到了一个 \(k\) 固定的一次函数,使其经过不同的 \((x,y)\)(与 \(j\) 有关),可以得到不同的截距 \(b\)(与 \(F_i\) 有关),其中取截距最大或最小,可以得到 \(F_i\) 的最大或最小值,因题而异。

具体地,我们维护一个凸包,取一次函数与切点为决策点。

2.2. 常见技巧

posted @ 2023-07-06 20:05  Eon_Sky  阅读(27)  评论(0编辑  收藏  举报