关于二分栈优化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\n",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)^2\le Dp[B]+(P[D]-P[B]-L)^2\]
\[Dp[B]+(P[C]-P[B]-L)^2\le Dp[A]+(P[C]-P[A]-L)^2\]
可以得到:
\[(P[D]-P[A]-L)^2+(P[C]-P[B]-L)^2\le (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\n",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|^K\le Dp[B]+|P[D]-P[B]-L|^K\]
\[Dp[B]+|P[C]-P[B]-L|^K\le Dp[A]+|P[C]-P[A]-L|^K\]
可以得到:
\[|P[D]-P[A]-L|^K+|P[C]-P[B]-L|^K\le |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|^K\le |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式满足决策单调性。
(证完单调性后就和玩具装箱一样了,故这里就不给代码了 )

后记

打表法好啊。。。

原文地址:https://www.cnblogs.com/ftotl/p/11961278.html

时间: 2024-10-08 14:55:27

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

csp-s模拟测试50(9.22)「施工(单调栈优化DP)」&#183;「蔬菜(二维莫队???)」&#183;「联盟(树上直径)」

改了两天,终于将T1,T3毒瘤题改完了... T1 施工(单调栈优化DP) 考场上只想到了n*hmaxn*hmaxn的DP,用线段树优化一下变成n*hmaxn*log但显然不是正解 正解是很**的单调栈 可以想象到最优情况一定是将两端高于中间的一段平原填成一段平的坑,不然如果坑内存在高度差那么我们即使只将一部分抬升也肯定没有用处,并且如果中间的坑已经高于了两端,再向上升也肯定不优,然后就中间的坑可以很很小,也可以很长,对于这个模型我们首先想到n^2*h的DP 设当前表示的f[i]表示当前到了i节

Codeforces Round #344 (Div. 2) E. Product Sum 二分斜率优化DP

E. Product Sum Blake is the boss of Kris, however, this doesn't spoil their friendship. They often gather at the bar to talk about intriguing problems about maximising some values. This time the problem is really special. You are given an array a of

Gym - 101981B Tournament (WQS二分+单调性优化dp)

题意:x轴上有n个人,让你放置m个集合点,使得每个人往离他最近的集合点走,所有人走的距离和最短. 把距离视为花费,设$dp[i][k]$表示前i个人分成k段的最小花费,则有递推式$dp[i][k]=min\{dp[j][k-1]+w(j,i)\}$,其中$w(j,i)$可以$O(1)$求出. 显然,如果考虑段数的话,光状态数就有n^2个,肯定行不通.不过这题的最优解对段数的函数是凸的,因此可以用WQS二分来打破段数的限制. 给每个集合点加上一个额外的花费c,然后忽略段数的限制,这样递推式就变成了

暑假D9 T2 extra(单调栈优化DP)

题意 一条路被划分成了n段,每一段有一个高度,一个人有步幅k,代表他最多可以从第x段一步到第x+k段,当h[x]>h[x+k]时,不消耗体力,否则消耗一点体力,求最后到第n段路时最少消耗的体力,最初在第一段路. 对于 30%的数据,保证 T = 1:对于另 20% 的数据,保证 N <= 500:对于 100% 的数据,保证 n< = 1e6, T <= 10,ai在 int 内,0 < K <n. 题解 很容易想到转移方程 f[i]= h[i]<h[j] ? f

二分算法 再次理解

二分算法 再次理解 详解二分查找算法 这篇博客很详细介绍了二分算法的一些细节问题 寻找一个数,也是最基本的二分搜索 //代码示例如下 int bsearch(int []nums, int target) { int left=0, right=nums.length-1;//这里的数组长度用法可以是其他的形式 while(left<=right) { int mid = left + (right - left) / 2; if(num[mid] == target) return mid;

集训第四周(高效算法设计)N题 (二分查找优化题)

原题:poj3061 题意:给你一个数s,再给出一个数组,要求你从中选出m个连续的数,m越小越好,且这m个数之和不小于s 这是一个二分查找优化题,那么区间是什么呢?当然是从1到数组长度了.比如数组长度为10,你先找5,去枚举每一个区间为5的连续的数,发现存在这样的数,那么就可以继续往左找,反之则往右找,直到左右区间重合,即为正确答案,(有可能是右区间小于左区间,所以每次都应该求区间中点值) #include"iostream" #include"set" #incl

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

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4709 课上讲的题,还是参考了博客...:https://www.cnblogs.com/GXZlegend/p/8615607.html 这道题和之前写的斜率优化不同的一点是用单调栈维护上凸壳,而且需要二分查找答案: 为什么感觉每次写出来的斜率优化DP都不一样...还是没有理解透彻... 代码如下: #include<iostream> #include<cstdio> #i

动态规划专题(五)——斜率优化DP

前言 斜率优化\(DP\)是难倒我很久的一个算法,我花了很长时间都难以理解.后来,经过无数次的研究加以对一些例题的理解,总算啃下了这根硬骨头. 基本式子 斜率优化\(DP\)的式子略有些复杂,大致可以表示成这样: \[f_i=min_{j=1}^{i-1}(A(j)-B(j)*S(i)+C(i))\] 其中\(A(j)\)和\(B(j)\)是两个只与\(j\)有关的函数,\(S(i)\)和\(C(i)\)是两个只与\(i\)有关的函数,式子中的\(min\)其实也可以替换成\(max\),但这里

『土地征用 Land Acquisition 斜率优化DP』

斜率优化DP的综合运用,对斜率优化的新理解. 详细介绍见『玩具装箱TOY 斜率优化DP』 土地征用 Land Acquisition(USACO08MAR) Description Farmer John is considering buying more land for the farm and has his eye on N (1 <= N <= 50,000) additional rectangular plots, each with integer dimensions (1