• 队列优化和斜率优化的dp


    可以用队列优化或斜率优化的dp这一类的问题为 1D/1D一类问题

    即状态数是O(n),决策数也是O(n)

    单调队列优化

    我们来看这样一个问题:一个含有n项的数列(n<=2000000),求出每一项前面的第m个数到它这个区间内的最小值

    可以使用RMQ求区间最小值,那么时间复杂度是O(nlogn),不是让人很满意。

    dp[i]为i-m+1->i这个区间的最小值。

    那么状态转移方程是

    可以看出,这个题目的状态数是O(n),决策数是O(m),且决策的区间是连续的,那么可以尝试想办法把O(m)优化成O(1)

    我们可以用单调队列维护一个数据结构,这个数据结构有两个域,pos和val,pos代表下标,val代表该下标所对应的值。队列中的pos单调递增,且val也单调递增

    那么当计算一个状态时,只要从队首不断弹出pos<i-m+1的数据,只要pos>=i-m+1,那么该决策就是最优的,因为队列是单调的啊。

    同理,同队尾插入一个数据时,只要不断剔除val比a[i]大的数据,直到遇到小于它的,然后将该数据插入队尾。

    每个数据只入队列,出队列一次,所以时间复杂度是O(n),

    分析:为什么插入的时候,比a[i]大的数据可以剔除,因为j<i时,a[j] > a[i], 那么以后所有的决策中,a[i]都比a[j]更优

      为什么可以不断删除pos<i-m+1的数据,因为i是递增的,该数据对当前的i没用,那么对以后的i也是没用的。

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <algorithm>
     5 #include <iostream>
     6 #include <queue>
     7 #include <stack>
     8 #include <vector>
     9 #include <map>
    10 #include <set>
    11 #include <string>
    12 #include <math.h>
    13 using namespace std;
    14 #pragma warning(disable:4996)
    15 #pragma comment(linker, "/STACK:1024000000,1024000000")
    16 typedef long long LL;                   
    17 const int INF = 1<<30;
    18 /*
    19 */
    20 const int N = 100 + 10;
    21 int a[N];
    22 int dp[N];
    23 int q[N], head, tail;
    24 int main()
    25 {
    26     int n, m;
    27     while (scanf("%d%d", &n,&m) != EOF)
    28     {
    29         for (int i = 1; i <= n; ++i)
    30             scanf("%d", &a[i]);
    31         head = tail = 0;
    32         q[tail++] = 1;
    33         dp[1] = a[1];
    34         for (int i = 2; i <= n; ++i)
    35         {
    36             while (head < tail && a[i] < a[q[tail - 1]])//插入新的元素,要使得队列依旧单调递增
    37                 tail--;
    38             q[tail++] = i;
    39             while (head < tail && q[head] < i - m + 1)//剔除不合要求的pos
    40                 head++;
    41             dp[i] = a[q[head]];
    42         }
    43         for (int i = 1; i <= n; ++i)
    44             printf("%d ", dp[i]);
    45         puts("");
    46         /*
    47         5 3
    48         1 2 3 4 5
    49         1 1 1 2 3
    50         */
    51     }
    52     return 0;
    53 }
    View Code

    那么我们可以抽象出一类模型

    需要注意到,上面要求可选的决策集是连续的。同时也可以注意到,当前决策所需要的值是不受现在的状态所影响的,即g(i)与w[x]是相互独立的。

    斜率优化

    我们在单调队列的最后说道,当前决策所需要的值是不受现在的状态所影响的,即g(i)与w[x]是相互独立的。

    还有的1D/1D一类问题是想下面这样的。

    但是如果状态转移方程是这样的: dp[i]=dp[j]+(x[i]-x[j])*(x[i]-x[j]) ,1<=j<=i   把括号化开后,得到2x[i]*x[j], 这使得当前决策所需要的值受当前状态的影响

    所以上面单调队列的方法就不行了。

    hdu3507

     题目有n个字符要打印,连续打印k个字符的代价是(c1+c2+...+ck)^2 + m, m是题目所给的常量

    题目要优化的是,如果连续打印过多,那么代价平方之后就会很大,如果连续打印过少,那么就多加几次m。

    dp[i]表示打印第i个字符时的最小花费

    dp[i] = min(dp[i],dp[j] + (sum[i]-sum[j])^2+m)  1<=j<i

     那么复杂度是O(n^2),是无法接受的。

    那么就需要优化了,

    当j > k 且j比k优的时候

    dp[j] + (sum[i]-sum[j])^2 + m < dp[k] + (sum[i]-sum[k])^2+m

    化简得(dp[j]+sum[j]^2 - (dp[k]+sum[k])^2 )/ (2sum[j]-2sum[k]) < sum[i]

    这就很像斜率表达式了,而且这个斜率表达式小于另一个斜率,即sum[i]。

    从下图我们可以看出,当j为k优时,斜率为sum[i]的斜线过j点与y轴的截距更小。

    令g[j,k]表示上面的式子

    当k<j<i时,且g[i,j] <= g[j,k]时,j是可舍弃的。

    如果所示,假设j成为最优,那么必须有sum[i] > g[j,k], 且 sum[i] < g[i,j], 即 g[j,k] < sum[i] < g[i,j],  也即有g[i,j] > g[j,k],但是与前提条件g[i,j] <= g[j,k]矛盾,所以假设不成立,所以j是可以舍弃的。

    所以,我们要维护一个下凸的图形(即斜率不断增大),因为横坐标(也就是sum[j])是递增的,所以用一个队列维护就行了。如果横坐标不递增的话,就要用平衡树了。

    如果,是一个下凸包,我们只要判断sum[i]所代表的斜线与哪个点在y轴上的截距最小就行了,又因为sum[i]是递增的,所以前面判断过的点不用再次判断。

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <algorithm>
     5 #include <iostream>
     6 #include <queue>
     7 #include <stack>
     8 #include <vector>
     9 #include <map>
    10 #include <set>
    11 #include <string>
    12 #include <math.h>
    13 using namespace std;
    14 #pragma warning(disable:4996)
    15 #pragma comment(linker, "/STACK:1024000000,1024000000")
    16 typedef long long LL;                   
    17 const int INF = 1<<30;
    18 /*
    19 普通的dp需要遍历以前的所有值,
    20 斜率dp就是通过舍弃一些值,必须是当前可舍弃,以后也是可舍弃的值,从而减少遍历量
    21 或者是使得队首的元素就是最优的,
    22 
    23 
    24 关键是如何判断可舍弃,。。。。。通过数学分析斜率来舍弃???
    25 
    26 
    27 
    28 第i个字符肯定接在前面j个字符的后面,或者另起一行,但是费用该怎么算呢
    29 dp[i][1]表示1另起一行 ,那么费用是 dp[i][1] = ci^2 + m + min(dp[i-1][0],dp[i-1][1])
    30 dp[i][0] 表示接在前面j个字符的后面的最小费用    sum{cost[j]}^2+m + min(dp[j-1][0],dp[j-1][1])
    31 
    32 */
    33 
    34 const int N = 500000 + 10;
    35 int sum[N];
    36 int dp[N];
    37 int q[N], head, tail;
    38 int getUp(int i, int j)
    39 {
    40     return dp[i] + sum[i] * sum[i] - (dp[j] + sum[j] * sum[j]);
    41 }
    42 int getDown(int i, int j)
    43 {
    44     return 2 * sum[i] - 2 * sum[j];
    45 }
    46 int main()
    47 {
    48     int n, m;
    49     while (scanf("%d%d", &n, &m) != EOF)
    50     {
    51         for (int i = 1; i <= n; ++i)
    52         {
    53             scanf("%d", &sum[i]);
    54             sum[i] += sum[i - 1];
    55         }
    56         head = tail = 0;
    57         q[tail++] = 0;
    58         for (int i = 1; i <= n; ++i)
    59         {
    60             /*
    61         
    62             */
    63             while (head + 1 < tail && getUp(q[head + 1], q[head]) <= sum[i] * getDown(q[head + 1], q[head ]))
    64                 head++;
    65             dp[i] = (sum[i] - sum[q[head]]) * (sum[i] - sum[q[head]]) + m + dp[q[head]];
    66             while (head + 1 < tail && getUp(i, q[tail - 1])*getDown(q[tail - 1], q[tail - 2]) <= getUp(q[tail - 1], q[tail - 2])*getDown(i, q[tail - 1]))
    67                 tail--;
    68             q[tail++] = i;
    69         }
    70         printf("%d
    ", dp[n]);
    71     }
    72     return 0;
    73 }
    View Code

    总结,

    当横坐标递增,斜率递增时,用队列维护,可以在O(1)的时间内找到最优值,就是上面的情况。

    当横坐标递增,斜率不递增时,我们可以二分,因为凸包的斜率是递增的,所以可以二分。

    当横坐标不递增时,用平衡树维护。

    【参考文献】

    《1D1D动态规划优化初步》 作者:南京师范大学附属中学 汪一宁

    《用单调性优化动态规划》   JSOI2009集训队论文

    《斜率优化dp》

  • 相关阅读:
    Go语言基础介绍
    webpack教程——css的加载
    Git忽略规则.gitignore梳理
    vue2.0路由-适合刚接触新手简单理解
    JAVA实现DES加密实现详解
    计算机网络: IP地址,子网掩码,网段表示法,默认网关,DNS服务器详解
    Storm:最火的流式处理框架
    神片和神回复
    论C#未来发展
    弄清UTF8和Unicode
  • 原文地址:https://www.cnblogs.com/justPassBy/p/4719207.html
Copyright © 2020-2023  润新知