zoukankan      html  css  js  c++  java
  • 线性DP总结吧。。

    线性DP总结 吧。。

    最近感觉做了很多线性DP和背包的简单题,又感觉背包也珂以算进线性DP,所以就一起写了吧

    下面的几个分类模型来源于AcWing

    基础的线性DP~

    包括几个基础的模型~

    M 数字三角形类的模型

    线性DP弟中弟(数字三角形)

    尽管它的思路非常的简单,但是毕竟是一个用二维数组设计状态来DP(或者说递推,这个弟中弟甚至不配被称作DP)的开端。

    直接来看例题的拓展~

    天真的例题~

    只是稍微过渡一下,毕竟多了个 min/max(可能这才是DP开端8)


    P1004 方格取数

    这道经久不衰的线性DP进阶例题其实真的很有意义的

    我们先来观察(O(n^4)) 的暴力转移:

    定义 (f[i][j][x][y]) 为两条路径,一个从((1,1))走到了((i,j))

    一个从((1,1))走到了((x,y)) 珂以获得的贡献最大值

    整个转移也很简单,就是在 x=i 而且 y=j 时减去重复贡献的点就好了

    虽然这是一个早期(2000) tg的一道原题,但是 0202 年谁还考这种暴力DP题a,于是我们要考虑优化:

    1.空间优化:
    我们的两条路径都是同时拓展的,因为只能向下或者向右走,我们完全珂以通过一个路径长度 k 用两条路径终点的横坐标算出纵坐标。

    时间 (O(n^4)) 空间 (O(n^3))

    2.空间的进一步优化:

    发现每一次的状态转移 ( k ) 只与上一层 (k-1) 有关,诶,这不就直接滚了么 完全珂以套用滚动数组的思想,用 ^1 取出上一层的值再转移

    空间(O(n^2))

    虽然说大多数的二维DP的时间是很难优化的

    但是空间在一些情况下是很好优化的:

    可以通过以下几种思路优化空间:

    1.通过设计不同状态来优化空间

    2.通过同一种状态的不同表示方法来优化空间

    3.发现状态转移只与上一层有关,直接滚动数组


    还有一些线性转移 而且用一维数组设计状态的DP,

    珂以用数据结构优化时间,这到时候再写8(估计到时候都退役了)

    D 上升子序列模型

    1.简单的 (n^2) 设计方程:

    (f[i]) 为以 (a_i) 结尾的最长上升子序列的长度

    整个的状态转移也就显而易见了

    f[i]=max(f[i],f[j]+1) (a[j]<a[i]) 
    

    通过上面这个简单的转移方程珂以类比到最长(不下降,不上升,上升)子序列中

    有了这个 (n^2) 你就可以尝试解决很多简单题了,

    P1091 合唱队形

    题目中说想要移除最少的人,简单来说也就是保留最多的人

    观察题目条件珂以发现:能保留在一个人左侧的人一定从左到右形成一个上升序列,同理右侧也从右到左满足珂以形成一个上升序列。

    所以你正着跑一遍最长上升,再倒着跑一遍最长下降

    那么答案 = (n-max(f1[i]+f2[i]-1))
    -1 是因为两个序列都有一个 (a_i)

    考虑优化时间

    (n^2) 的出来了,而且又是一个一维数组设计的DP,空间很难优化了,那么考虑优化时间:

    (值域线段树优化DP找区间最大值简单粗暴快捷)

    考虑一个数组存储整个最长上升子序列:

    那么对于一个从前向后扫将 扫描到的数 加入这个数组中的过程:

    显而易见:如果说这个数 > 数组的尾部数,就可以将它加入数组

    那么一个贪心策略也就珂以想出来:

    如果让末尾的数更小,就可以让更多的数加入这个序列

    那么对于 (leq) 末尾数的数

    考虑将它替换数组中的一个数,答案是替换第一个 > 它的数,

    (感性理解,我也不会证明)

    1.替换了不影响当前最长上升子序列的长度

    2.替换了 > 它的数就可以让更多的数加入

    因为整个数组中是单调的,所以可以采用二分解决替换的问题

     for(int i=2;i<=n;i++){
        if(a[i]>f[ans]){ans++;f[ans]=a[i];}
        else{f[upper_bound(f+1,f+ans+1,a[i])-f]=a[i];}
      }
    

    进阶例题:

    首先考虑暴力:

    设计状态(f[i][j]) 表示(a[1~i])(b[1~j])

    珂以组成的最长公共上升子序列长度:

    状态转移过程:

      for (int j = 1; j <= n; j ++ ){
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j]){
                int maxn = 1;
                for (int k = 1; k < j; k ++ )
                    if (a[i] > b[k]) maxv = max(maxn, f[i - 1][k] + 1);
                f[i][j] = max(f[i][j], maxn);
            }
        }
    

    时间复杂度:(O(n^3))

    考虑优化时间:

    发现 maxn 是 满足 a[i] > b[k] 中整个(b[k]) 集合中 (f[k]+1) 最大值

    所以珂以直接将 maxn 提出去

     for (int i = 1; i <= n; i ++ ){
            int maxn = 1;
            for (int j = 1; j <= n; j ++ ){
                f[i][j] = f[i - 1][j];
                if (a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
                if (a[i] > b[j]) maxv = max(maxv, f[i - 1][j] + 1);
            }
        }
    

    时间复杂度:(O(n^2))

    在考虑优化空间:

    又发现 这个转移只跟上一层有关。。所以又珂以滚动数组了。。

    for(int i=1;i<=n;++i){
        int maxn=0 ;
        for(int j=1;j<=n;++j){
          if(a[i]>b[j]){maxn=max(maxn,f[j]);}
          else if(a[i]==b[j]){f[i]=max(f[j],maxn+1);}
        }
      }
    

    (个人觉得滚动数组版本要比二维数组的版本要好想一点。。。)

    背包 ~ ~ ~ ~

    简单的 01 背包~ ~ ~

    转移非常的简单~ ~ ~ ~

     for(int i=1;i<=m;i++){
       for(int j=0;j<=t;j++)  {
         if(j>=w[i])dp[i][j]=max(dp[i-1][j-w[i]]+value[i],dp[i-1][j]);
         else   dp[i][j]=dp[i-1][j];                
       }
     }
    

    诶,我们又发现这个东西只跟上一层状态有关了,诶这就直接滚动了

    (需要注意的是 01 背包需要倒序枚举,因为他需要的是 i-1 层的状态,如果正序枚举的话可能会用到第 i 层的状态

    完全背包:

    就滚动数组版的 01 背包正序枚举就好了,因为它正好需要第 i 层的状态。。。。

    持续咕咕咕

  • 相关阅读:
    Linux基础-3.用户、群组和权限
    Linux基础-2.目录文件的浏览、管理及维护
    Linux基础-1.Linux命令及获取帮助
    CentOS6.10安装详解
    有序字典
    根据公历计算农历
    常用模块
    人工智能_2_特征处理.py
    人工智能_1_初识_机器学习介绍_特征工程和文本特征提取
    python-matplotlib
  • 原文地址:https://www.cnblogs.com/NuoCarter/p/14448890.html
Copyright © 2011-2022 走看看