zoukankan      html  css  js  c++  java
  • 集训Day 7 2020.3.7 动态规划(二)

    集训Day 7 2020.3.7

    动态规划(二)

    状态压缩DP

    我们目前碰到的题的状态都比较好表示。但有时我们也会记录具体的状态。此时为了节省时间和空间,我们就可以使用状态压缩来对状态压缩。因为我们是记录具体的状态,所以数据范围一般不会很大,所以当你看到数据范围小但是暴力又过不去(或者写起来可能十分困难)的时候就要想到状压dp了。

    1.LGP1896[SCOI2005]互不侵犯

    在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下
    八个方向上附近的各一个格子,共8个格子。
    (1 le N le 9, 0 le K le N^2)

    题解

    不难发现,我们上一行如何放国王会影响下一行如何放国王,但是上两行及以上放的国王是不会影响到我们这一行放的国王的。
    所以我们只需要记录上一行的国王放到了哪里。
    我们令没有国王的格子为0,有国王的格子为1,那样我们就把这个状态变成了二进制了。
    (f[i][j][k])表示前(i)行放(j)个国王且第(i)行排成(k)情况的时候的情况数有多少。
    为了方便运算与表示,我们定义(g[i])表示一行国王排成i情况的时候有几个国王。
    显然,我们(f[i][j][k]+=f[i-1][j-g[l]][l])但是前提(l)(k)不能互相侵犯,并且自己行内的国王不能互相侵犯。
    复杂度(O(n^32^n2^n))?
    合法的状态转移数其实并没有这么多,因此能过。

    小trick

    g怎么算?除了暴力?

    g[i]=g[i>>1]+(i&1);
    

    如何判断状态(i,j)之间是否合法?
    首先判断(i)(j)本身是否合法,通过将本身左移,再和原状态&一下,如果不为0就一定撞上了。
    再考虑(i)(j)之间是否合法,同样的思路,将(j)左/右移/不动和(i)&(当然反过来也可以),如果不为0就一定撞上了(这分别对应了国王可以攻击左,中,右三个格子)

    背包dp

    这是一项大类
    里面不涉及多重背包,混合背包,泛化物品等知识点(不常用(真不常用,有些算法我

    2.P1008 采药

    有N件不可切割的物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

    题解

    这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
    不难想到f[i][v]表示前i件物品放入一个容量为v的背包可以获得的最大价值,这样答案即为f[N][M]。
    初始化所有f为0,其状态转移方程便是:

    f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
    

    (前者就是不放,后者就是放)
    当然前提是能放得下物体,不然的话就要

    f[i][v]=f[i-1][v]
    

    以上方法的时间和空间复杂度均为(O(NV)),时间复杂度已经没法优化了,但是空间复杂度还可以进一步优化,这里就插播一种优化的方式:

    滚动数组

    思考我要求fib第n项我需要开多大的数组?
    n+1?我可以告诉你只需要3个就行了。
    f[2]=f[1]+f[0];f[0]=f[1];f[1]=f[2].
    上述循环n-2次,答案就是f[2]。
    不难看出优化原理:我们每次需要的其实就是f[n],f[n-1],f[n-2],至于其他的项已经没有用了,所以完全可以扔掉。

    0/1优化

    现在我们再回顾背包问题,我们只需要f[i]和f[i-1],所以处理方法就如同前面所说, 第一维完全可以滚掉,这样我们的空间复杂度就有保证了。
    但第一维其实也没用。
    我们设f[v]表示容量为v的背包可以获得的最大价值,递推式子没有变还是

    f[v]=max{f[v],f[v-c[i]]+w[i]}
    

    但是有一个问题,可能这个f[v-c[i]]是已经放入了一遍物品,于是就有可能造成我们一件物品多拿的情况!
    考虑到v-c[i]<v,我们在枚举第二维的时候直接倒着枚举就好了。

    3.P1616 疯狂的采药

    (N)种物品和一个容量为(V)的背包,每种物品都有无限件可用。第(i)种物品的费用是(c_i), 价值是(w_i)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

    题解

    与0/1背包不同的是每个物品都可以无限取。于是

    f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]}
    

    后者的-1去掉了是因为我们虽然取了一次i物品但是我们仍然可以继续去取它。
    当然,我们仍然可以优化掉前面的一维状态,还记得0/1为什么倒着循环吗?
    我们正着循环就是完全背包的解法!

    for(int i=1;i<=n;i++){
        for(int v=w[i];v<=m;v++){
            f[v]=max(f[v],f[v-w[i]]+c[i]);
        }
    }
    

    4.P1855 榨取kkksc03

    二维费用的背包问题是指:对于每件物品, 具有两种不同的费用;选择这件物品必须 同时付出这两种代价;对于每种代价都有 一个可付出的最大值(背包容量)。问怎 样选择物品可以得到最大的价值。

    题解

    设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为(a_i,b_i)。两种代价可付出的最大值(两种背包容量)分别为(V)(U)。物品的价值为(w_i)
    解法就是多加一维就好了,如果是0/1就按0/1循环,如果是完全就完全,道理都一样。

    分组背包

    5.P1757 通天之分组背包

    (N)件物品和一个容量为(V)的背包。第(i)件物品的费用是(c_i),价值是(w_i)。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

    题解

    实际上和普通的0/1背包一样,我们多加一维状态就好了。设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有

    f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于第k组}
    

    完后写三层循环即可。
    但是我们也可以完全不用多加这维,如下:

    for 所有的组k
        for v=V...0
            for 所有的i属于组k
                f[v]=max(f[v],f[v-w[i]]+c[i]);
    

    三层循环的位置不能颠倒

    要求背包必须装满

    思考只装一个第一个物体的时候

    f[v]=max{f[v],f[v-w[i]]+c[i]}
    

    我们是从(f[v- w[i]])转移过来的,而(v-w[i])不一定为(0),也就是说(v-w[i])为多少,最后这个背包就会剩下多大的空间。
    所以说白了要求背包必须装满就是要求我们背包必须要从f[0]开始转移,于是我们有以下解决方法:

    1. 要求背包必须装满且求最大值
      f[0]=0, 其余为负无穷。
    2. 要求背包必须装满且求最小值
      f[0]=0, 其余为正无穷。

    背包变形

    输出具体方案

    以0/1背包举例子。记录下每个状态的最优值是由状态转移方程的哪一项推出来的, 换句话说,记录下它是由哪一个策略推出 来的。便可根据这条策略找到上一个状态, 从上一个状态接着向前推即可。

    比如(g_{i,v})表示(f_{i,v})这层状态是否取了第(i)件物品,如果取了就是(1),否则就是(0)
    然后我们从(f_{N,V})倒推,如果(g_{N,V}=1)那就说明它取了第(N)件物品,那么它之前的状态就是(f_{N-1,V-w_N});
    否则就没取,然后从(f_{N-1,V})转移来的。一直循环下去就能输出方案了。
    但当然(f)不必真的设两维,(g)设两维就好了。

    6.输出字典序最小的具体方案P1759

    这要求我们转移的时候细致些,比如说取i和不取i得到的价值都是一样的的话我们就不能要。
    但输出方案的时候请注意,我们输出的是反过来的,因为我们是从后往前推的。
    比如说你取法为1 3 4,你输出就会得到4 3 1,所以需要事先存起来然后倒着输出。

    7.求将背包装满方案数P1164,P1474

    将f[i][j]定义改一下变成方案数即可,把max改成sum就好,因为这个状态可以从这两个状态转移来,所以方案数就是不取i的方案数+取i的方案数,即f[i][v]=sum{f[i-1][v],f[i- 1][v-c[i]]+w[i]},初始条件f[i][0]=1。
    当然本质上还是背包所以我们前一维可以去掉。

    8.求最优方案的总数

    f定义不变,我们再定义g表示方案总数。如果f[v]从f[v-w[i]]+c[i]更新过来了,那么g[v]=g[v-w[i]]。但是注意,如果取和不取一样的话,那么g[v]+=g[v-w[i]]

    预告

    树形dp

    要做就做南波万
  • 相关阅读:
    docker安装wnameless/oracle-xe-11g并运行(手写超详细)
    QQ浏览器兼容模式问题
    list中放map的几种方式
    oracle存储函数实例
    vue中使用Element主题自定义肤色
    使用eclipse初步学习vue.js基础==》v-for的使用 ②
    使用eclipse初步学习vue.js的基本操作 ①
    发布网站的流程演示
    bookkeeper table service调研
    python爬取今日头条街拍
  • 原文地址:https://www.cnblogs.com/liuziwen0224/p/xjx7.html
Copyright © 2011-2022 走看看