2021冲B层暑假集训Day5 DP专题

文章目录

展开

Day5 动态规划

零、什么是动态规划

简单来说,DP是一种决策方式。

这种决策方式需要每次决策依赖于当前状态,随机有引起状态的转移,从而获得一个决策序列。

基本思想就是,待解决的问题分成若干个子问题,依次求子问题的解,前一子问题的解为后一子问题提供有效的信息。在求解子问题时,列出所有可能的局部解,通过决策保留有可能达到最优的局部解。最后一个子问题就是初始子问题的解。

比如我们熟知的斐波那契数列f[n],我们都知道f[i]=f[i-1]+f[i-2]

那么假设我们的问题是求f[10],那么我们就可以分成求f[10],f[9],f[8]…的子问题,而先求的子问题为后求的子问题提供了有效信息(即可以代入递推式子求解)。

当然这只是个例子,一个递推式子还谈不上用了动态规划。

动态规划三要素:

1.问题的阶段,比如上面的,,i,i-1,i-2…

2.每个阶段的状态,也就是这个f数组所表示的状态

3.相邻阶段的递推关系,比如上面的f[i]=f[i-1]+f[i-2]

当然我们也可以用二维的数组去表示一个状态,如

f[i][j]可以表示一个东西的状态在第i行第j列时,或一个东西的状态在取了i个点j条边。

动态规划的过程是:初始状态→决策1→决策2→…→决策n→终止状态​

初始状态就是要知道某一位置的状态是已知的,比如上面例子中的f[0]=1,f[1]=1

终止状态就是要求答案的状态,比如上面例子中的f[10]

一、背包DP

(一)01背包

1.问题描述

有n个物品,容量为m的背包。第i个物品有价值vi和体积wi,想从n个物品中选出若干个装到背包里,体积和不能大于m,求价值和的最大值。

2.解决方法

我们用状态f[i][j]表示我们已经考虑了前i个物品,选取的物品占了j的容量时,所能取到的最大价值。

对于第i个物品,我们可以考虑选它或者不选它来进行转移,如果选了的话,那么这个状态就是从f[i-1][j-w[i]]来的;如果不选的话,这个状态就是从f[i-1][j]来的。

f[i][j]=max{f[i-1][j],(f[i-1][j-w[i]]+c[i])}

最后的答案就是max{f[n][0],f[n][1],…,f[n][m]}

3.优化空间

(1)滚动数组

我们再来考虑斐波那契数列,我们要求f[i]的话,只需要f[i-1]f[i-2],因此我们没有必要开f[n]的数组,只需要开f[3]f[0]=f[1],f[1]=f[2],f[2]=f[0]+f[1],循环下去即可。

(2)滚动数组优化01背包

我们来看f[i][j],如果将i删掉的话:

f[j]=max(f[j],f[j-w[i]]+v[i]);

我们发现每层循环里的f[j]依然是f[i][j],但是等号右边的f[j-w[i]]却不一定是f[i-1][j-w[i]],因为正序枚举,在前面修改f[j1]的值的时候可能恰巧修改到了f[j2-w[i]],即j1=j2-w[i]。(此处j1<j2)

这样的话,我们就可能将一个物品选取多次。

那么修改方法就是:我们枚举j的时候倒序枚举

我们知道,在上面的代码中倒序枚举和正序枚举没有叙别,但是在删掉f后,倒序枚举让j1不可能等于j2-w[i]。(此处j1>j2),那么我们就可以使用一维的f[j]进行背包了。

(3)例1:[NOIP2005普及组]采药:luoguP1048传送门

题目大意:一道裸的01背包。

实例代码:

(二)完全背包

1.问题描述

有n种物品,每种物品有无穷个,容量为m的背包。第i种物品有价值vi和体积wi,想从无穷个物品中选出若干个装到背包里,体积和不能大于m,求价值和的最大值。

2.解决方法

我们发现,完全背包和01背包的区别就是,01背包是有限个,而完全背包是有限种无限个。

我们刚刚在讨论优化空间时强调,顺序枚举j的话可能将一个物品选取多次,而我们现在的目的就是将一个物品选取多次。

所以解决方法很简单,将01背包中逆序枚举j改成正序枚举即可。

例2:疯狂的采药:luoguP1616传送门

题目大意:一道裸的完全背包,注意数据范围要开long long。

实例代码:

(三)多重背包

1.问题描述

有n种物品,容量为m的背包。第i种物品有价值vi和体积wi,第i种物品有ci个。想从sigma(ci)个物品中选出若干个装到背包里,体积和不能大于m,求价值和的最大值。

2.解决方法

我们发现这次的区别是每种物品的数目有限且不一定为1。

如果ci很小的话,我们可以把ci个物品拆成一个一个的物品进行01背包。

但如果ci很大呢,比如1000,那么就有1000000个物品。显然是不可行的。

这个问题有两种解法:

解法一:二进制拆分

我们考虑把k个物品利用二进制拆成logk个。

比如有某种物品有10个,那就可以拆成4个(1+2+4+3),这样我们就可以组合出任意的物品数了。

如果我们想要8个物品,就可以取4个里的第一、三、四个。

这样我们就把ci拆成logci个了。这样就只有10000个物品。可能就可行了。

这种算法的复杂度是O(n·logci·m)的。

解法二:单调队列优化

我们考虑一个朴素的DP转移式:

f[i][j]=max{f[i-1][j],(f[i-1][j-w[i]]+c[i]),(f[i-1][j-2×w[i]]+2×c[i]),…}

我们考虑DP的j,将%w[i]同余的j单拎出来。

假设w[i]=4,c[i]=2。我们拎出%4=0的jf[i-1][0],f[i-1][4],f[i-1][8],f[i-1][12]…

f[i][0]会从f[i-1][0]转移过来;

f[i][4]可以从f[i-1][4]转移过来,也可以从f[i-1][0]+c[i]转移过来;

f[i][8]可以从f[i-1][8]转移过来,也可以从f[i-1][4]+c[i]转移过来,还可以从f[i-1][0]+2×c[i]转移过来;

f[i][12]可以从f[i-1][12]转移过来,也可以从f[i-1][8]+c[i]转移过来,还可以从f[i-1][4]+2×c[i]转移过来,但值得注意的是, 此时是不可以由f[i-1][0]+3×c[i]转移来的,因为c[i]=2,这个物品只有两个。

这样看下来,我们仿佛看到了一个长度为2的窗口在滑,且每次需要的是窗口内最大的值来进行转移,所以我们可以考虑单调队列。

大家可以手动模拟一下单调队列的过程,加c[i]的系数可以通过j的值来计算。

然后对于%4=1,%4=2,%4=3的情况也一样,我们发现都跑完之后恰好把所有的情况都跑了一遍。

因此这个算法的复杂度是O(n·m)的。

例3:宝物筛选:luoguP1776传送门

单调队列优化代码:

二进制拆分代码:

(四)分组背包

1.题目描述

现在有n组物品,每组物品有ci件,第i组的第j件的体积是wij,价值是vij,背包容量m。现在要求取若干个物品,要求总体积不能大于背包容量,且每组物品里最多只能取一个,求最大价值。

2.解决方法

这种题的不同在于,物品和物品之间是有限制的。

我们考虑一种暴力的做法就是,我们枚举每一组,对于第一个分组的物品,如果取了的话,就直接判断下一组的物品,否则判断该组的下一种物品。

也就是说,这里的f[i][j]表示的是前i组物品花费v时的最大价值。

f[i][j]=max{f[i-1][j],f[i-1][j-c[k]]+w[i]},其中物品k是属于第i组的。也就是说,我们取f[i][j]时,取出的是第i组里最优的那一个物品。

例4:通天之分组背包:luoguP1757传送门

题目大意:分组背包裸题

实例代码:

当然背包里还有一些附件问题的题,大家需要具体问题具体分析。

二、树形DP

(一)树形DP的实现

树形DP的状态是,对于第i个结点,有f[i][j]的状态,表示以i为根的子树上满足j条件时的最优解,j可以是选择j个结点,或者是删除j条边等等。

(二)为什么要用树形DP

树这种结构可以很好的判断某点与其附属结点的关系,而且可以递归的用儿子修改父亲的值。

前者可以对应解题,后者正是DP的精髓。

(三)例题时间

1.没有上司的舞会

我们先来看一道经典的例题:没有上司的舞会:luoguP1352传送门

题目大意:有n个职员要参加舞会。n个职员有像树一样的从属关系,这导致除老板(根节点)他们都有一个直接上司。我们知道每个员工都有欢乐值,但是如果某个员工的直接上司来参加了,他就一定不会来。现在让我们求满足要求的欢乐值最大值。

解题思路:我们设f[i][0]表示以i为根的子树上如果i不参加舞会的最大值,设f[i][1]表示以i为根的子树上如果i参加舞会的最大值。那么我们可以显然知道:(j表示i的直接下属)

f[i][0]=sigma{max{f[j][0],f[j][1]}},即如果第i个人不来,那就可以考虑他的直接下属来还是不来。

f[i][1]=sigma{f[j][0]},即如果第i个人来了,那么就让他的直接下属都不来了。

最后答案就是max{f[root][0],f[root][1]}

实例代码:

2.时态同步

我们再来看一道稍微进阶一点的例题:时态同步:luoguP1131传送门

题目大意:有一棵树,树上的边有边权。所有根到所有叶子结点的距离都可求。现在有一个操作Q可以使某一条边的边权加一,问最少经过多少次操作Q使得根到所有叶子结点的距离都相同。

解题思路:我们考虑状态f[i]表示以i为根的子树最多需要多少次操作,并维护一个数组maxn[i]表示以i为根的子树到叶子结点距离的最大值。那么我们就有状态转移方程:

f[u]=sigma{f[v]+maxn[u]-w[u,v]-maxn[v]},其中u,v分别是一条边的起点和终点,w[u,v]表示这条边的长度。然后我们进行树上DP就可以了。

实例代码:

三、状压DP

(一)什么是状态压缩动态规划

状压一般适用于在n×n的棋盘上进行操作。

状压DP是将状态用二进制的方法存起来。

比如我们有一个9×9的棋盘,那么某一行的棋子方法就可以用一个数字去表示:比如(283)_{10}就可以表示成:

列数 1 2 3 4 5 6 7 8 9
二进制 1 0 0 0 1 1 0 1 1
是否放

由此我们可见,一个状态只需要2^{n+1}-1的十进制数就可以表示。

我们还发现这样的话会遍历每种状态,而一行最多有2^n种状态,所以我们会发现一般这种题的n值都很小。(如果太大的话也不好用十进制数表示)

(二)一些二进制小技巧

与或非,异或,位运算就不再多说了。

1.判断一个数字x二进制下第i位是不是1

if(((1<<(i-1))&x)>0):将1左移i-1位,相当于制造了一个只有第i位是1,其他位都是0的数。再与x做与运算,如果结果大于0,则说明x二进制下第i位是1.

2.将一个数字x二进制第i位变成1

x=x|(1<<(i-1)):证法类似1.

3.将一个数字x二进制第i为变成0

x=x&~(1<<(i-1)):证法类似1.

4.把一个数字二进制下最靠右的第一个1去掉

x=x&(x-1):手推一下很好找规律。

(三)例题时间

1.互不侵犯

经典的状压DP题:互不侵犯:luoguP1896传送门

题目大意:N×N的棋盘放k个国王,国王能攻击到与自己相邻的八个格子。问有多少种摆放方案使他们互不攻击

解决思路:

实例代码:

四、数位DP

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注