关于二分栈优化DP算法的理解

Posted ftotl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于二分栈优化DP算法的理解相关的知识,希望对你有一定的参考价值。

引入

二分栈主要用来优化满足决策单调性的DP转移式。
即我们设(P[i])(i)的决策点位置,那么(P[i])满足单调递增的性质的DP。

由于在这种DP中,满足决策点单调递增,那么对于一个点来说,以它为决策点的点一定是一段连续的区间。

所以我们可以枚举以哪个点作为决策点,去找到它所对应的以它为决策点的区间。
考虑如何找到一个点的区间:

可以发现,在当前情况下(枚举到以某个点作为决策点的情况下),该点所对应的区间一定为[L,N].(L可能等于N+1)

那么我们可以用一个栈来存储区间[L,N]中的L,每次新枚举到一个决策点(i),就用栈顶L判断,看L是用原决策点更优,还是用新决策点(i)更优。
因为满足决策单调性,所以若用新决策点更优的话,该L就没有意义了,就直接可以从栈顶弹出。
我们一直执行以上操作,直到遇到一个L的原决策点比新决策点(i)更优,那么说明这个L还是有意义的,所以不能弹。
然后我们就需要去二分一个点出来作为新的L,使得这个点右边的点以(i)为决策点更优,左边的点以(i)为决策点更劣。
以上就是二分栈的基本思路。

举个例子:
决策点:1111111111 栈:1(1)
决策点:1112222222 栈:1(1) 4(2)
决策点:1112222233 栈:1(1) 4(2) 9(3)
决策点:1112224444 栈:1(1) 4(2) 7(4)
注:栈里应该有两个信息,一个是L,一个是转移点.
(我们不能维护每个点的转移点,那样会提高时间复杂度)

代码实现思路:
①定义一个队首指针,对于目前枚举到的决策点(i),若(i)未被队首指针的区间包含,那么指针前移,直到(i)被包含,然后更新(i)的DP值。((i)的决策点就是目前队首指针所对应的转移点)
②判断目前栈顶的L以(i)为决策点更优,还是以原决策点更优。若以(i)更优,弹出栈顶,然后,循环往复②操作。
③对于目前的栈,判断一下,栈是否为空:

  • 若为空,直接让新的信息入栈。
  • 若不为空,二分新决策点L的位置(此处所有点的原决策点都是目前栈顶的原决策点),入栈。
    (注:记得特判L!=N+1)

小结

对于大多关于二分栈的题,一般是发现有单调性后就直接套版了。
所以在使用二分栈时,一般需要先证明DP的决策单调性(一般使用打表法证明),限制还是很大。
注:有转移限制的DP对二分栈限制很大,只有在限制也满足单调性的情况下才能用。
(比如CSP2019D2T2划分就可以用类二分栈做法过掉(O(N*log(N)))能过的所有点)

#include<cstdio>
#include<algorithm>
using namespace std;
const long long ONE=1;
const int MOD=(1<<30);
const int MAXM=100005;
const int MAXN=40000005;
const long long INF=4e18;
int N,TYP,Pt[MAXN];
long long A[MAXN],Dp[MAXN];
int Stac[MAXN],ID[MAXN],L,R;
void Prepare(){
    scanf("%d%d",&N,&TYP);
    if(TYP==1){
        int X,Y,Z,M;
        int P[MAXM]={0},B[MAXN]={0};
        scanf("%d%d%d%d%d%d",&X,&Y,&Z,&B[1],&B[2],&M);
        for(int i=3;i<=N;i++)B[i]=(ONE*B[i-1]*X+ONE*B[i-2]*Y+Z)%MOD;
        for(int i=1,L,R;i<=M;i++){
            scanf("%d%d%d",&P[i],&L,&R);
            for(int j=P[i-1]+1;j<=P[i];j++)
                A[j]=B[j]%(R-L+1)+L;
        }
        return ;
    }
    for(int i=1;i<=N;i++)
        scanf("%lld",&A[i]);
}
int main(){
    Prepare();
    for(int i=1;i<=N;i++)
        A[i]=A[i-1]+A[i];
    for(int i=1;i<=N;i++){
        while(Stac[L+1]<=i&&L<R)L++;
        long long x=A[i]-A[ID[L]];
        Dp[i]=Dp[ID[L]]+x*x;Pt[i]=ID[i];
        int l=i,r=N+1;
        while(L<=R&&A[Stac[R]]-A[i]>=x)R--;
        if(L>R){Stac[++R]=i+1;ID[R]=i;continue;}
        while(l+1<r){
            int mid=(l+r)/2;
            if(x<=A[mid]-A[i])r=mid;
            else l=mid;
        }
        if(r==N+1)continue;
        Stac[++R]=r;ID[R]=i;
    }
    printf("%lld
",Dp[N]);
}

例题

其实主要是证单调性,其它的部分都比较版。

T1玩具装箱

(虽说这是个斜率优化板题呢...)
最终核心大意:给出了(P)数组与一个常数(L),其中(P)数组满足单调递增的性质。
有一个Dp转移式:(Dp[i]=min{Dp[j]+(P[i]-P[j]-L)^2};)
单调性证明如下:
采用反证:设有(A,B,C,D(A<B<C<D)),其中(A)(D)的最优决策点,(B)(C)的最优决策点。(即要证明这种情况不存在)
那么有[Dp[A]+(P[D]-P[A]-L)^2le Dp[B]+(P[D]-P[B]-L)^2]
[Dp[B]+(P[C]-P[B]-L)^2le Dp[A]+(P[C]-P[A]-L)^2]
可以得到:
[(P[D]-P[A]-L)^2+(P[C]-P[B]-L)^2le (P[D]-P[B]-L)^2+(P[C]-P[A]-L)^2]
化简得:
[2*(P[B]-P[A])*(P[D]-P[C])le0]
与条件不符,故不存在这种情况,即证明该Dp有决策单调性。

#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=50005;
int N,Len,A[MAXN],Pt[MAXN];
long long S[MAXN],Dp[MAXN];
int Stac[MAXN],ID[MAXN],L,R;
long long W(int i,int j){
    return (S[i]-S[j]-Len)*(S[i]-S[j]-Len);
}
int main(){
    scanf("%d%d",&N,&Len);Len++;
    for(int i=1;i<=N;i++)
        scanf("%d",&A[i]),S[i]=S[i-1]+A[i];
    for(int i=1;i<=N;i++)S[i]+=i;
    for(int i=1;i<=N;i++){
        while(Stac[L+1]<=i&&L<R)L++;
        Dp[i]=Dp[ID[L]]+W(i,ID[L]);
        while(L<=R&&Dp[ID[R]]+W(Stac[R],ID[R])>=Dp[i]+W(Stac[R],i))R--;
        if(R<L)Stac[++R]=i+1,ID[R]=i;
        else{
            int l=i,r=N+1;
            while(l+1<r){
                int mid=(l+r)/2;
                if(Dp[ID[R]]+W(mid,ID[R])>=Dp[i]+W(mid,i))r=mid;
                else l=mid;
            }
            if(r==N+1)continue;
            Stac[++R]=r;ID[R]=i;
        }
    }
    printf("%lld
",Dp[N]);
    return 0;
}
/*
Dp[i]=Min{Dp[j]+W(i,j)};
*/

T2诗人小G

最终核心大意:给出了(P)数组与一个常数(L)及一个参数(K),其中(P)数组满足单调递增的性质。
有一个Dp转移式:(Dp[i]=min{Dp[j]+|P[i]-P[j]-L|^K};)
单调性证明如下:(沿用T1的思路)
采用反证:设有(A,B,C,D(A<B<C<D)),其中(A)(D)的最优决策点,(B)(C)的最优决策点。(即要证明这种情况不存在)
那么有[Dp[A]+|P[D]-P[A]-L|^Kle Dp[B]+|P[D]-P[B]-L|^K]
[Dp[B]+|P[C]-P[B]-L|^Kle Dp[A]+|P[C]-P[A]-L|^K]
可以得到:
[|P[D]-P[A]-L|^K+|P[C]-P[B]-L|^Kle |P[D]-P[B]-L|^K+|P[C]-P[A]-L|^K]
然后......
我们设(X=P[B]-P[A],Y=P[C]-P[B],Z=P[D]-P[C];)
技术图片
那么有:[|X+Y+Z-L|^K+|Y-L|^Kle |Y+Z-L|^K+|X+Y-L|^K]
我们不妨画出(F(t)=|t-L|^K)的图像,就像这样:
技术图片
然后在图像上将那四个点标出来。
发现((X+Y+Z-L)+(Y-L)=(Y+Z-L)+(X+Y-L)),即这四个点的横坐标是关于(E=frac{X+2*Y+Z}{2})对称的。
但由于那四个点的分布情况繁多,所以不妨分类讨论(由于左边右边本质是一样的,所以这里只讨论一边的情况):
①:左二右二(左边两个点,右边两个点)
技术图片
这种情况下,显然(F(Y)+F(X+Y+Z)ge F(X+Y)+F(Y+Z))
故与条件不符。
②:左一右三(左边一个点,右边三个点)
技术图片
那么这种情况下,我们将(Y)翻转至(Y`),那么此时有(DX1<DX2,DY1<DY2),即[F(Y+Z)-F(Y)=F(Y+Z)-F(Y`)<F(X+Y+Z)-F(X+Y)]
即有[F(Y+Z)+F(X+Y)<F(X+Y+Z)+F(Y)]
故与条件不符。
③:左零右四(左边零个点,右边四个点)
技术图片

这种情况下有(DX1=DX2),由函数斜率递增的性质可得(DY1<DY2)
故同②的情况,与条件不符。

综上,不存在给出情况,故该Dp式满足决策单调性。
(证完单调性后就和玩具装箱一样了,故这里就不给代码了 )

后记

打表法好啊。。。

以上是关于关于二分栈优化DP算法的理解的主要内容,如果未能解决你的问题,请参考以下文章

bzoj 4709 [ Jsoi 2011 ] 柠檬 ——斜率优化DP

二分+DP+单调队列优化绿色通道 LibreOJ - 10181

算法优化》关于1D*1D的DP的优化

关于LIS和一类可以用树状数组优化的DP 预备知识

R语言实战应用-lightgbm 算法优化:不平衡二分类问题(附代码)

[noip科普]关于LIS和一类可以用树状数组优化的DP