zoukankan      html  css  js  c++  java
  • 复习笔记-动态规划

    关于DP的总结与心得(实时更新)


     

    DP可以算是最灵活多变的一种题型,没有固定的算法或者公式,重点在于熟悉每个类型的基本模板。

    类型(一)——背包问题

    背包问题是我们最早接触的DP类型,类型有很多,今年的真题D1T2就可以看成一个完全背包。

    01背包

    传送门

    在这类问题里面,对于每种物品有ci和wi两个值,并且只有选择和不选择两种状态,需要求出在有限的Σc中使Σw最大

    状态转移方程:f[i][j] = max(f[i][j - 1], f[i - 1][j - ci] + wi) i表示计算到第i个物品,j为当前背包容量

    这样的问题,标准思路是开一个二维数组,但是由于每个数据只和上一行有关,可以使用滚动数组,但是更好的办法是降维。

    如何降维?

    我们假设f[i]表示刚好装满i空间时的最大价值,j为当前状态,那么关于任何一个物品,f[i + ci] = max(f[i + cj], f[i] + wj)

    注意:每一次装包时要从后往前,不然相同物品会被重复放入,最后输出答案不能直接输出f[m],而是输出f[1]到f[m]的最大值

    其他背包问题

    完全背包:每个物品选取任意次,传送门,正向遍历即可

    组合背包:有些物品选取时必须选取另一种物品,基础方程的max中考虑这些情况即可 传送门

    部分背包:每个物品可以选取非整数个,c与w成比例,这个最简单,直接求性价比然后贪心就o98k了

    多维背包:背包容量有多个参数,这玩意还是记忆化搜索比较靠谱传送门 传送门

    类型(二)——线性动归

    典型模板:最长上升子序列

    这个就比较简单了,对于每个状态f[i],在f[1]到f[i - 1]中选取最大值加一即可

    例题:导弹拦截 合唱队形

    类型(三)——树形动归

    这种问题一般分为两种,我暂且将它成为邻居问题和裁剪问题

    邻居问题

    大意:当一个节点被标记后,它的邻居节点就会被覆盖,求覆盖整个树的最少标记

    这种问题说白了只能记忆化搜索,因为你遍历一棵树也是基于DFS的操作

    例题:P2016 P2899

    裁剪问题

    对树进行裁剪,只能删除,不能改变以前的结构,在留下的边权在m之内,使剩余点权最大

    这种问题可以当做背包来考虑,但是要从叶子往根判断

    例题:P2015 P2014

    类型(四)——区间动归

    这种问题大体思路就是枚举区间长度,然后在区间内按照题意模拟即可

    例题:P1880

    类型(五)——状压动归

    这种问题一般是在一个二维矩阵上,修改一个点一般会对上下左右的点产生影响,一般我们使用一个二进制数存一行的状态(每一位01表示是否覆盖),然后与上一行与,左右位运算再与上一行与,看是否为零来判断有没有互相影响,这样就能把状态压缩到一行。

    例题:P1896 P1879

    类型(六)——单调队列与斜率优化

    单调队列

    这种问题一般是区间最值,一般使用一个队列,把小于后点的前点删掉,过期点删掉,每次就可以只考虑队尾

    例题:P1886 P1725

    斜率优化(这玩意我其实也讲不明白是怎么肥四)

    斜率优化:对于一些特殊的DP,它的状态转移方程可以转换成一次函数的形式,然后用单调队列处理它每个状态的斜率(图像上凸的点删掉),就可以省略掉很多状态,然后移动截距即可

    例题:P2365

     一点心得——记忆化搜索

    第一眼看到动规的题,如果想不出思路,一定要用Dfs打一个暴力,但是在打完暴力之后,也许修改一下Dfs,就可以成为复杂度正确的记忆化搜索

    以下内容from LSY

    以这道题为例:传送门

    这是一个典型的Dfs暴力程序:

    int n,t;
    int tcost[103],mget[103];
    int ans = 0;
    void dfs( int pos , int tleft , int tans ){
        if( tleft < 0 ) return;
        if( pos == n+1 ){
            ans = max(ans,tans);
            return;
        }
        dfs(pos+1,tleft,tans);
        dfs(pos+1,tleft-tcost[pos],tans+mget[pos]);
    }
    int main(){
        cin >> t >> n;
        for(int i = 1;i <= n;i++)
            cin >> tcost[i] >> mget[i];
        dfs(1,t,0);
        cout << ans << endl;
        return 0;
    }
    

      然后开一个二维数组,修改一下Dfs函数,就成为了记忆化

    int n,t;
    int tcost[103],mget[103];
    int mem[103][1003];
    int dfs(int pos,int tleft){
        if( mem[pos][tleft] != -1 ) return mem[pos][tleft];
        if(pos == n+1)
            return mem[pos][tleft] = 0;
        int dfs1,dfs2 = -INF;
        dfs1 = dfs(pos+1,tleft);
        if( tleft >= tcost[pos] )
            dfs2 = dfs(pos+1,tleft-tcost[pos]) + mget[pos];
        return mem[pos][tleft] = max(dfs1,dfs2);
    }
    int main(){
        memset(mem,-1,sizeof(mem));
        cin >> t >> n;
        for(int i = 1;i <= n;i++)
            cin >> tcost[i] >> mget[i];
        cout << dfs(1,t) << endl;
        return 0;
    }
    

     

      优点:好想

      缺点:没法使用滚动数组,要是内存占用大的话只能考虑降低维度

  • 相关阅读:
    第二阶段冲刺第二天
    第二阶段冲刺第一天
    第十四周学习进度条
    《探索需求-设计前的质量》阅读笔记六
    《探索需求-设计前的质量》阅读笔记五
    xxx征集系统项目目标文档
    《探索需求-设计前的质量》阅读笔记四
    《探索需求-设计前的质量》阅读笔记三
    将一台电脑上的虚拟机上的系统复制到另一台电脑的虚拟机上!!!and想询问大神们问题的解决办法??
    《探索需求-设计前的质量》阅读笔记二
  • 原文地址:https://www.cnblogs.com/Juruo1103/p/10098122.html
Copyright © 2011-2022 走看看