集训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

posted @ 2020-03-10 12:18  刘子闻  阅读(196)  评论(0编辑  收藏  举报