$Dynamic Planning Optimization$ 关于动态规划的优化方案(%$color{red}{rqy}$)

Posted yeasio-nein

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了$Dynamic Planning Optimization$ 关于动态规划的优化方案(%$color{red}{rqy}$)相关的知识,希望对你有一定的参考价值。

关于动态规划的优化方案(%(color{red}{rqy}))

1.单调队列

单调队列是一种具有单调性的队列,其中的元素全部按照递增或者递减的顺序排列,就比如下面这个递减队列。

技术分享图片

假如说我们要在队尾加入一个(5),那么我们入队的步骤就是这样的:

发现队尾(1),(q[tail]),(1<5),则将1退出(tail--)

发现队尾(2),(q[tail]),(2<5),则将2退出(tail--)

发现队尾(3),(q[tail]),(3<5),则将3退出(tail--)

发现队尾(8),(q[tail]),(8>5),停止退出队尾,将(5)入队。

经过上述步骤之后队列变为了{8,5},依然满足递减的单调性,而实际上这也就是单调队列的基本操作。而维护递增的方式也是一样的。

#define MAXN 100010
int n,a[MAXN];
int q[MAXN],head=1,tail=1;
for(int i=1;i<=n;i++){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);//输入 
    q[1]=a[1];//将第一个元素入队 
    for(int i=2;i<=n;i++){
        while(head<=tail&&q[tail]<a[i])
        //如果队列不为空并且队尾元素小于a[i] 
            tail--;//弹出队尾元素 
        q[++tail]=a[i];//入队 
    }
}

【例题1】

我们现在有一个整数序列(A(a[MAXN])),长度为(n),又知两个参数(k)(m),要求:从(A)序列中找出(k)个不相交的区间,每段区间长度(len)<=(m),要求所有k个区间的区间和最大。

考虑最基本的(DP),设(dp[i][j])表示从前(j)个数里面选出来(i)个长度不超过m的不相交区间的区间和最大值,然后我们再枚举一个(k),指选择([k+1,j])这个子区间。然后我们创造一个前缀和数组(sum[MAXN]),那么([k+1,j])这个区间的区间和就是(sum[j]-sum[k])。子问题分为两块:(j)选入子区间,或者(j)不选入子区间,从(j-m)(j)范围内枚举一个(k)使得(dp[i-][k]+sum[j]-sum[k])最大,然后与(dp[i][j-1])取一个(max)可得答案。


for(int i=1;i<=n;i++)
    sum[i]=sum[i-1]+a[i];//前缀和数组
for(int i=1;i<=k;i++)
for(int j=1;j<=n;j++){
    int ans=-INF;
    for(int k=j-m;k<=j;k++){
        ans=max(ans,dp[i-1][k]+sum[j]-sum[j]);
    }
    dp[i][j]=max(ans,dp[i][j-1]);
}

这样的(DP)时间复杂度为(O(nmk)),显然太大,于是我们考虑优化。

我们可以看到(DP)的原式子是(color{red}{dp[i][j]=max(dp[i][j-1],max(f[i-1][k]+sum[j]-sum[k]));})

我们发现在里面的(k)的最优化枚举当中,sum[j]是不随k的枚举变化的,所以我们可以将sum[j]提出来变成:(color{red}{dp[i][j]=max(dp[i][j-1],sum[j]+max(f[i-1][k]-sum[k]));})

可以知道在整个式子里面最耗时间的就是最后关于(dp[i-1][k]-sum[k])最大值的枚举,所以只要快速计算出来了(dp[i-1][k]-sum[k])就可以快速计算整个式子。我们来看(dp[i-1][k]-sum[k])的范围是在([0][0],[0][1],....[0][m-1],[1][m],[2][m+1],...,[n-m][n-1])这些区间上的最大值,也就是所有的([j][i+j-1])的区间。

技术分享图片

我们发现这些区间的左右端点都是单调递增的,所以我们可以利用单调队列在(O(1))的时间内解决这些区间。然后我们就将时间优化到了(O(nk))

一个(n×m)的矩形网格。你初始站在((x,y))这。有些格子有障碍而有些没有。有(K)个时间段。第(i)个时间段从(s[i])持续到(t[i])(包括两端)这段时间内网格会向某个方向(上下左右之一)倾斜。所以每个时间段内的每个时间单位,你可以选择在原地不动,或者向倾斜的方向走一格(当然你不能走到障碍上或是走出网格)。

求你最多能走多少格。

技术分享图片

如上图所示,黑色方块为障碍,(S)为起始点。

按照最常的(DP)思路来看,我们设(dp[k][i][j])为在k时间点,从((x,y))节点走到了((i,j))节点的时候最长走了多长。初始化(dp[0][i][j])全部为(?∞),而(dp[0][x1][y1]0)=(0)((x1,y1)为初始位置),考虑子问题就是:从那边来?(k)时刻是从那个方向来还是不动?我们以第(k)时刻向右倾斜为例。

技术分享图片

如果是向右倾斜,那么上一层状态就是在((i,j-1))地点,那么结合两个子问题我们可以得出(DP)方程式:(dp[k][i][j]=max(dp[k-1][i][j],dp[k-1][i][j-1]+1);)

for(int k=1;k<=len;k++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        dp[k][i][j]=max(dp[k-1][i][j],dp[k-1][i][j-1]+1);

那么这样的时间复杂度就是(O(nmsum_{i=1}^{K}(t[i]-s[i]+1))),是无法通过这个题的全部数据的。然后我们紧接着考虑怎么优化。关于位置的(n^2)枚举我们没有什么办法,但是关于(K)我们可以进行优化,时间点很多有(sum_1^K(t[i]-s[i]+1))个,但是时间段(K)却<=(200),那么我们可以将一段时间的转移全部合并起来一起算,那么就快得多了。

我们设(dp[k][i][j])为在第(k)个时间段末尾,从((x,y))走到了((i,j))点,(len[k])为第(k)个时间段的持续时间,可以算出是(t[k]-s[k]+1)

首先还是(n^2)的枚举,和(k)时间段的枚举,之后我们还有一个(l)的枚举,这个(l)枚举的是上一个状态加上在当前这个(k)的时间段内一共走的步数对应倾斜方向的横、竖坐标,如果我们继续以右倾为例,那么(j-len[j]<=l<=j),就是从完全不动到走了最多的(len[k])步,那么我们有了状态转移方程式:(color{red}{dp[k][i][j]=max_{j-len[k]<=l<=j}(dp[k-1][i][l]+j-l)}),由于其中的+(j)与l的枚举并无关联,所以提出来就变成了(color{red}{dp[k][i][j]=max_{j-len[k]<=l<=j}(dp[k-1][i][l]-l)+j})。其实也就是枚举这个时间段之前这个人的位置在哪,也就知道了当前的(dp[k][i][j])是从哪里转移过来的。

之后,我们回过头来看上一道题的最后的(DP)方程式:(color{red}{dp[i][j]=max(dp[i][j-1],sum[j]+max(f[i-1][k]-sum[k]));})

是不是发现格式非常的相似呢?,我们固定住(i)之后的状态转移方程式基本是和上题一样的,所以一样可以使用单调队列优化到(O(nmK))

下面针对一组样例,我们进行一遍手动模拟,以帮助更好的理解。

就用洛谷的样例吧。(第一行分别为n,m,x1,y1,k)

4 5 4 1 3

. . xx.

. . . . .

. . . x.

. . . . .

1 3 4

4 5 1

6 7 3

那么画完图之后就是这个样子:

技术分享图片

(1)~(3)时刻的倾斜方向是右,那么纵坐标是你不变的,我们枚举纵坐标。

for(int i=1;i<=k;i++){
        int s; int t; int dir;
        scanf("%d%d%d",&s,&t,&dir);
        //注意要反着DP,也就是倒退
        int len=t-s+1;
        if(dir==1)  //北面(上)
            for(int j=1;j<=m;j++)//北面的话横坐标不变,那么我们枚举纵坐标
                DP(i,n,j,dir,len);
        if(dir==2)  //南面(下)
            for(int j=1;j<=m;j++)//南面的话横坐标不变,那么我们枚举纵坐标
                DP(i,1,j,dir,len);
        if(dir==3)  //西面(左)
            for(int j=1;j<=n;j++)//西面的话纵坐标不变,那么我们枚举横坐标
                DP(i,j,m,dir,len);
        if(dir==4)  //东面(右)
            for(int j=1;j<=n;j++)//东面的话纵坐标不变,那么我们枚举横坐标
                DP(i,j,1,dir,len);
    }

然后当我们的横坐标x枚举到1的时候,我们在DP函数里面定义一个now,然后是(while(x>=1)&&(x<=n)&&(y>=1)&&(y<=m)),因为首先要保证不超过边界。然后如果我们发现右面是可以走的,那么我们就进行一个push操作。也就是关于dp[p-1][x][y]在单调队列里面的入队操作。在最前面我们已经介绍了。

void push(int now,int value){
    if(value==-INF) return ;
    //如果压根做不到这里,那么直接返回
    while(head<=tail&&value-now>=q[tail])
        tail--;//弹出队尾
    q[++tail]=value-now;
    pos[tail]=now; 
    //pos记录位置,用来判断是不是可以滑
}

而至于为什么要在(while)里面减去一个(now),是因为(x,y)这个位置不一定是在当前方向的起点上,因为之后某一步的步数减去当前的步数得到的值就是(x,y)到那一步在的点的距离,相当于一个化简~

由于(dp[0][i][j])=-(INF),当前的(p)=(1)所以(p)-(1)的时候(value)就是-(INF),所以在第0个时间段到不了这个地方,我们直接返回。然后下面其实就没什么事了,所有的push全部直接返回,最后退出DP函数。就这样进行到(x)(即(j))=(3)的时候,我们发现(map[3][4])是一个障碍点,那么也就是说我们之前进行的所有工作全部无效,然后我们将整个队列清空,即(head)=(1,tail)=(0);

然后接着进行到(x)=(4),(y)=(1)((4)(1)列)的时候,我们到了起始点,而起始点的dp[0][4][1]是0,所以(value)!=-(INF),我们终于将一个值(value)-(now)=-1入队了,那么我们当前的队列是这个样子的:

技术分享图片

加上步数之后我们发现(dp[p][x][y]=q[head]+now)依然是(0),所以(ans)没有被更新(废话,你从起点走到起点需要更新(ans)嘛),所以我们继续向下进行,因为每次(now)都会++,所以下面的(dp[p][x][y])加上(now)之后就可以更新(ans)的值了。然后进行到(x)=(4),(y)=(5)的时候,我们发现(now-pos[head]=4),大于可以(len),也就是说超过了可以滑动的区间。(一共就三秒你怎么滑第四块啊~)那么我们将队首弹出,接下来我们就不能再更新ans的最大值了,(x)=(4)时完美结束。这个时候我们的行走路径大概如下:

技术分享图片(蓝色方块为当前方块,黄色方块为路径)

也就是说从(1)~(3s)我们最多可以走3块。(真是麻烦啊~)

(i)继续走,我们进行到下一个时间段。(4)~(5s)的时候是向北倾斜的。那么我们进行(DP(i,n,j,dir,len)),我们从(n)(j)列开始(DP),第一次将(tail)弹出后又入队我们不管,因为(j=1)(2)的时候都不能更新(ans),然后到了(j=3)的时候,我们将(dp[1][4][3]-now=1)入队了。

技术分享图片

然后当(now)进行到第三次的时候我们就可以更改ans值为4了。

之后结束了第二个时间段。此时的路径大概是这样的:

技术分享图片

最后在第三个时间段内,我们将路径更改为如下:

技术分享图片

那么以上就是整个样例的模拟,最终我们得到(ans)数为6.

关于单调队列优化的一点总结

鉴于两者之间的(DP)转移方程的相似性,我们成功的利用单调队列优化了问题,那么回过头来看看,什么样的问题可以利用单调队列进行优化呢?我们最上面讲的单调队列是具有单调性的一种数据结构,他可以保证数据的单调性,自然也就可以留下数据的最大值或者最小值,利用了单调性,就是减少了一位枚举,减去一维,直接获得单调队列里面的最优解。并且DP可以使用单调队列优化,当且仅当(DP)式的格式基本满足(color{red}{dp[i]=a[i]+max_{l[i]<=j<=r[i]}b[j]})的时候。即“(dp[i])=(A(i))+(B(j))中的最小/大值 ((i-k<=j<i,k)为常数())”,当你发现要求(max)而且求可能拓展的状态有线性关系的时候,你就可以考虑单调队列优化了。

以上是关于$Dynamic Planning Optimization$ 关于动态规划的优化方案(%$color{red}{rqy}$)的主要内容,如果未能解决你的问题,请参考以下文章

Project management and planning

markdown project-planning.md

B. Journey Planning

UVA11559 Event Planning水题

Journey Planning

C. Planning(贪心)