DP&图论 DAY 2 上午
背包DP模型
>背包DP
◦一般是给出一些“物品”,每个物品具有一些价值参数和花费参数,要求
在满足花费限制下最大化价值或者方案数。
◦最简单几种类型以及模型
◦ 0/1背包
◦完全背包
◦多重背包
>0/1背包问题
◦给出n个物品,每个物品有Vi的价值和Wi的费用,我们总共有m块钱,求
最多能得到多少价值的物品。
◦ N<=10^3,m<=10^3
>Solution
◦设dp[i][j]表示前i个物品,用了j的体积得到的最大的价值。
◦则dp[i][j]=max{dp[i-1][j] , dp[i-1][j-w[i]]+v[i]}
◦复杂度O(N*M)
◦如果要记录方案数呢?
◦如果要求输出方案呢?
>在有价值情况下求方案数??
原来的式子是dp[i][j]=max{dp[i-1][j] , dp[i-1][j-w[i]]+v[i]} ,dp[ ][ ] 是最大价值
现在设方案数为 f[ ][ ]
如果dp[i-1][j] > dp[i-1][j-w[i]]+v[i] ,那么就会选择第一种转移过来
如果dp[i-1][j] < dp[i-1][j-w[i]]+v[i] ,那么就会选择第二种转移过来
所以 f[i][j]=max(f[i-1][j],f[i-1][j-wi]}
如果dp[i-1][j] = dp[i-1][j-w[i]]+v[i] ,那么就两种都加起来
f[i][j]=f[i-1][j]+f[i-1][j-wi]
>完全背包
◦每一个物品可以选无限个。
◦ dp[i][j]=max{dp[i][j-w[i]],dp[i-1][j]}
◦在这里提一下一个贪心的预处理
◦对于所有 Vi≥Vj, Ci≤Cj 的物品 i,都可以完全扔掉
◦对于体积相同的物品只需要留下价值最大的物品
◦对于随即数据这个优化的力度非常大。
>多重背包
◦对每一个物品,最多能用t[i]次。
◦最暴力的方法就是转移的时候枚举这个物品选几个即可。
◦ dp[i][j] = max{ dp[i-1][j-w[i]*k] + v[i]*k | k<=t[i]}
◦复杂度O( N*M*t[i] )
多重背包
◦优化一:可以把t[i]拆成1+2+4+8…2^k+x, 这样k+1组, x小于2^(k+1) ,然后
我们会发现这些组能拼成0…t[i]每一种情况,然后这样我们就成了
n*log(t[i])个物品的0/1背包问题。
◦对于[0,2^(k+1)-1]都能拼成,所以[x,2^(k+1)-1+x]也都能拼成, x<=2^(k+1)-1,
则[0,2^(k+1)-1+x]全能拼成。
◦复杂度O(n*log(t[i])*m)
多重背包另一个优化
◦我们来观察一下式子
◦我们发现对于第二维,我们的j和能转移过来的j-w[i]*k在模w[i]意义下是同
余的,也就是说我们可以对于第二维按照模w[i]进行分类,不同类之间不
会互相影响。
◦设f[j]=dp[i-1][j*w[i]+r]。 r是我们枚举模w[i]的一个类。
◦实际上就是一个滑动窗口取最值的问题,直接单调队列优化即可。
◦单调队列均摊是O(m)
◦总复杂度就是O(n*m)的了。
多重背包的一个细节
◦首先我们之前都是在求最优值。单调队列和二进制分组的方法都是对的。
◦但是如果要求方案数呢?
◦这两种方法还都是对的吗?
>方案数求前缀和就不需要单调队列了
>分组背包
◦一共有n组,每组有size[i]个物品,第i组第j个物品的费用为w[i][j],价值
v[i][j],每个组里的物品是互斥的,意味着你在一组物品中只能选择一个
物品,求花费小于等于m能得到的最大价值。
◦ Size之和小于等于1000, m<=1000
经典例题
◦给出n个化学反应,每个反应消耗a[i]升氧气和b[i]升氢气,可以得到w[i]的
价值,现在总共有X升氧气和Y升氢气,求我们最多可以得到多少价值。
◦ n,a[i],b[i],X,Y<=100
>二维背包问题
◦此外还有二维背包问题,实质上和本身和普通背包毫无思维上的区别。
◦因为限制条件多了一个,所以我们需要给最初最基本的dp多加一维状态。
◦设dp[i][x][y]表示前i个物品,消耗了x的氧气和y的氢气所能得到的最大收
益是多少。
◦然后考虑一个物品选还是不选即可。
对于一般的背包问题
◦做背包问题最关键的就是找清楚并反问自己?
◦这题里面 什么是容量? 什么是物品? 什么是物品的费用? 什么是
物品的价值?
◦容量,就是这题当中我们怎样表示状态的数组。
◦费用,就是用来f[i]---->f[i+v[k]],状态转移的跨度。
◦价值,就是你这个dp的数组,所维护的东西。维护的数值!
◦背包dp一定要理解好这三点,因为很多时候题目中的“费用”并非背包
dp中的“费用”
>vijos1240
◦ 1.给你一些房间,告诉你这些房间的容纳人数和价格。
2.安排一定数量的人住到旅馆里,满足:
a.不同性别的人如果不是夫妻那么不能住一个房间。
b.一对夫妻如果住在一起,那么房间不能安排其他的人进去哪怕房间
没盛满人。
◦你来写一个程序帮助佳佳找到安排这些来参加旅行的人住进旅馆所需要
的最小花费。
◦ M:参加旅行的男性人数、 f:参加旅行的女性人数、 r:旅馆的房间数、
c:这些男女中有多少对夫妻、 Bi:每个房子容纳人数和、 Pi:每个房子
价格。注意每一个人不是单身就是和他/她唯一的妻子/丈夫一起参加旅行。
◦ 0<=m,f,r<=300, 0<=c<=Min(m,f), 0<=Pi<=10。 2<=Bi<=300
>Solution
◦还是要先明确,对于一道背包dp的题目来说,我们需要有容量,物品,
费用,价值(权值,因为有些题要求最小)。
◦本题中:求给所有的人安排房间的最小支出是多少?
那么在这里,几个人对应就是dp的数组下标
每个房间就是一个物品
房间支出就是物品的权值。
虽然这里看上去房间支出是花费,是作为数组下标的存在
实际上是作为我们要求的东西,是dp数组存的内容。
◦当然肯定不是这么简单就完了的。
>先观察一个性质
◦首先要观察出题目的一个小性质,即如果有两对(或以上)夫妻住在一起的
话,那么交换之后结果不会变差。因为首先这两个房间的容量至少为2,
如果男男在一个房间,女女在一个房间此时花费不变,有一个房间容量
大于2的时候,就还可以再入住其他人。这样结果变得更优了。 综上,要
么至多存在1对夫妻住在一起,要么不存在夫妻住在一起。
◦ f [ i,j,k,0 ]表示前 i 个房间住 j 名男性 k 名女性并且没有夫妇住在一起的最小花费
◦ f [ i,j,k,1 ]表示前 i 个房间住 j 名男性 k 名女性并且有一对夫妇住在一起的最小花费
◦ f [ i,j,k,0 ] = min( f[ i-1,j,k,0 ] , f[ i-1,j-v[i],k,0 ]+p[i] , f[ i-1,j,k-v[i],0 ]+p[i] )
◦ f[ i,j,k,1 ] = min( f[ i-1,j,k,1] , f[ i-1,j-v[i],k,1 ]+p[i] , f[ i-1,j,k-v[i],1 ]+p[i] , f[ i-1,j-1,k-1,0 ]+p[i] )
这题hin强
>1190: [HNOI2007]梦幻岛宝珠
◦给你N颗宝石,每颗宝石都有重量和价值V。要你从这些宝石中选取一些
宝石,保证总重量不超过W,且总价值最大为,并输出最大的总价值,每
颗宝石的重量符合a*2^b。
◦ V<=1e9
◦ a<=10; b<=30
>Solution
每颗宝石的重量符合a*2^b , 就是重量可以写成这个形式
二进制拆分一下
1 0 1 0 0 1 1 1 0
f[i][j]到前 i 位,剩下 j*2^i 的重量可用
◦看到w很大,看似不可做。
◦但是这题肯定能做啊!我们就找有什么特殊的约束条件。
◦ w=a*2^b,我们发现a, b都不大,就启发我们用2^b分组。
◦将物品按b值从大到小排序分阶段处理,在阶段内b值都相同,直接忽略
不足2^b的部分, f[i][j]表示前i个物品,剩余的能用重量为j*2^b的最大价
值。
◦从上一阶段到下一阶段时,将f[i][j]->f ’[i][j*2+零碎部分],注意到n=100,
a<=10,所以剩余重量最多纪录到1000即可。
◦复杂度O(n*1000)
几个背包变种和模型
>Bzoj3163 heoi2013 新背包问题
◦ N个物品,第i个物品有c[i]个,购买第i个物品需要a[i]元,可获利b[i]的价
值。有m个询问,每次询问:如果第x个物品禁止购买,你有y元的话,能
获得的最大价值是多少?询问之间互相独立。
◦ N<=1000,m<=3*10^5
>Solution
这是一个经典的问题
◦ 分治+背包
◦ 初始solve(1,n)
◦ 递归的函数到Solve(l,r),维护的dp数组,记录的是除去[l,r]外的物品的构成的背
包数组。
◦ Solve(l,mid)时,把[mid+1,r]内的物品加入dp数组。
◦ 我们这里定义的加入这个物品u,就是多考虑上这个物品之后构成的dp数组。
若是0/1背包的加入也就是做以下这个操作。
◦ For (int i=n;i>=w[u];i--) dp[i]=max(dp[i],dp[i-w[u]]+v[u]);
◦ 当l=r时,将对应所有的询问在dp数组查询即可。
◦ 单调队列优化的话,复杂度O(n*m*log(n)),每个物品被加进去log次,每次O(m)
代码实现
◦ Insert(dp,i):是在dp数组当中加入i号物品。
>bzoj2287
◦ ftiasch 有 N 个物品, 体积分别是 W1, W2, ..., WN。 由于她的疏忽, 第 i 个物
品丢失了。 “要使用剩下的 N - 1 物品装满容积为 x 的背包,有几种方法
呢?” -- 这是经典的问题了。
◦她把答案记为 Count(i, x) ,想要得到所有1 <= i <= N, 1 <= x <= M的 Count(i,
x) 表格。
◦ N,M<=3000
>Solution
◦设f[x]表示全部物品都可用恰好装满x体积时的方案数,可以用01背包算法
求出。这是总方案数。
◦然后考虑不选某物品的情况。
◦设g[x]为不选当前物品恰好装满x体积时的方案数。
f[i]加进去当前物品, g[i]不加
f[i]=f[i]+f[i-w[i]]
f[i]=g[i]+g[i-w[i]]
g[i]=f[i]-g[i-w[i]]
◦当x小于w[i]时, i物品一定不会被选上 g[i]=f[i]。
◦当x大于等于w[i]时, i物品可能会被选上,直接求不选的情况比较困难。
◦总方案数及f[x],不选的方案数可以想为先不选i再最后把i选上,即g[x-w[i]]。
◦所以g[x]=f[x]-g[x-w[i]], x从小到大枚举计算g即可。
◦每次都是线性复杂度,一共n次计算,总复杂度是O(n*m)
另两种理解方式
◦ 1:也可以用生成函数来理解。
◦ 2:直接考虑程序代码也可以很简单的理解。
>对比前两道题
◦第一道是求最优值,第二道是求方案数。
◦方案数是可以取逆的,但是最优值是无减法定义的。
◦所以第一道不能用第二道的方法也就是不能把复杂度降到O(n*m)。
◦如果第一题的Q是1000的话,可以有复杂度更优的做法吗?
>经典题
◦给出n个物品,每个物品有体积v[i],要求把物品分成两堆,然后使得两堆
物品体积的绝对值最小。
◦ n,v[i]<=100
>Solution
可行性的背包问题
◦我们设总体积为sum,那么我们必定会存在一堆体积和是小于等于sum/2
的,然后就是要求去选出一些物品总体积小于等于sum/2,最大能取到的
体积是多少。
◦这就是经典的背包问题了。
◦同时可行性的背包问题可以bitset优化
整数划分模型
一类整数划分问题
◦ 1:求把n划分成k个正整数的方案数?
◦ 2:求把n划分成互不相同k个正整数的方案数?
◦ 3:求把n划分成k个不大于m的互不相同正整数的方案数?
◦ 4:求把n划分成k个奇数的方案数?
◦ ………………………………………………
◦如果n,k和m是同阶的,我们看看最优能做到多少的复杂度。
求把n划分成k个正整数的方案数:
1.暴力dp
◦设dp[i][j][sum]前i个数,选了j个数,和为sum的方案数是多少,答案就是
dp[n][k][n],考虑这一个数选几个来转移即可。这个状态是n*k*n的转移
O(sum/i)。均摊O(n*k*n*log(n))复杂度有些高。
◦本质是个完全背包的,那种写法的话可以做到O(n*k*n)。
2.非暴力
◦我们直接设dp[i][j]表示把i划分成j个数的方案数。 ans=dp[n][k]
◦我们可以得到dp[i][j]=dp[i-j][j]+dp[i-1][j-1]。以下是dp[22][4]的两类情况:
考虑有没有1的部分。
◦我们考虑数形结合来理解。
◦考虑去掉蓝色的部分。
◦左图代表: dp[ i-j ][ j ]
◦右图代表: dp[ i-1 ][ j-1 ]
满足状态转移要考虑的所有可能性!
求把n划分成互不相同k个正整数的方案数:
1.暴力
◦大同小异。
◦暴力dp:设dp[i][j][sum]前i个数,选了j个数,和为sum的方案数是多少,答
案就是dp[n][k][n],考虑下一个数选不选来转移即可。
◦本质是个0/1背包, 复杂度O(n*k*n)
2.非暴力
◦正解也是大同小异。
◦我们还是直接设dp[i][j]表示把i划分成j个数的方案数。
◦我们可以得到dp[i][j]=dp[i-j][j]+dp[i-j][j-1]。考虑去掉蓝色的部分。
◦左图代表: dp[i-j][j]
◦右图代表: dp[i-j][j-1]
◦与上一题不同这里第一维
◦是[i-j],因为要限制没有相
◦同的数字。
◦这里复杂度比上题更低!!
求把n划分成k个不大于m的互不相同正整数的方案数。
◦背包dp的方法同上。
◦ N=20,k=4,m=6的不合法方案如图。
◦ dp[i][j]=
◦ dp[i-j][j]+dp[i-j][j-1]-dp[i-(m+1)][j-1]
◦减的这一项就是超过线的那种情况。
>BZOJ 3612
◦给定一个杠杆,等距均匀分布一共2n+1个等重质点,支点在n+1处,求拿
走k个质点后使杠杆仍然保持平衡的方案数 mod p的值。
◦ 1 <= n <= 10000, 1 <= k <= 10, 2 <= p <= 10000,且 k <= 2n+1
>Solution
左边距离之和=右边距离之和
◦其实就是-n~n中求选k个不同的数,和为0的方案数。
◦显然求出来f[i][j]表示选出j个数和为i的方案数,然后枚举其中一端拿走几
个a,以及拿走的数的重量之和x,把f[x][a]*f[x][k-a]累加之和就是最后的
答案了。
◦这里j个数是互不相同的,也就转化成了我们的“把n划分成互不相同k个
正整数的方案数”
◦算f的复杂度和统计答案的复杂度均是O(n*k*k)。
求把n划分成k个奇数的方案数?
可重复数字纯奇纯偶划分
◦ g[i][j]:将i划分为j个偶数
◦ f[i][j]:将i划分为j个奇数
g[i][j] = f[i - j][j]; f[i][j] = f[i - 1][j - 1] + g[i - j][j];
◦不可重复也是一样的。
由此类问题对状态转移的一点感触
◦ dp问题中,转移就是分情况讨论,每种情况对应一个方案数或最优值,
而这个方案数或最优值可以表示为之前已经求出来的dp值的组合。
◦只不过分情况讨论可能方法很多,以一种方式讨论能转化为已知的dp值
的叠加,另一种方式也可以。我们需要保证的是:讨论不漏掉任何情况,
像计数问题也不能出现方案重叠(求max和min其实是可以重叠的),同
时选择分类项数尽量少的方案,以便得到更优的复杂度。
◦上面几道题的转移方式可能没有原先一些问题的分类方式直观,但是也
的确满足了不重不漏尽量简洁的条件。当然这可能也并非是唯一的转移
方式,只要保证能划归到之前已经求出的dp值就行。
状态转移的一点感触
◦ 1:分类讨论要不重不漏。
◦ 2:讨论时要保证划归到的dp状态已经求出来,而不是还未求的。
◦以上保证正确性。
◦实际上能保证以上两点,无论怎样的转移方式都是可行的。
◦ 3:尽量分的类少一些,转移更快速。
◦这是关于复杂度。
总结
数位DP
数位dp
◦经典的数位Dp是要求统计符合限制的数字的个数。
◦一般的形式是:求区间[n,m]满足限制f(1)、 f(2)、 f(3)等等的数字的数量是
多少。 条件 f(i) 一般与数的大小无关,而与数的组成有关。
◦善用不同进制来处理,一般问题都是10进制和二进制的数位dp。
◦数位dp的部分一般都是很套路的,但是有些题目在数位dp外面套了一个
华丽的外衣,有时我们难以看出来。
>HDU3652
◦统计区间 [1,n] 中含有 '13' 且模 13 为 0 的数字有多少个。
◦ N<=10^9
>Solution
991366含13 9915366不含
HDU3652简化版
◦暴力的去枚举每一个数然后去计算必然太慢。
◦我们先来考虑一个更简单的形式
◦统计区间 [1,n] 中含有 '3' 的数字有多少个。
◦ N=x_1 x_2 x_3 x_4….. x_total 。 x_i为n的从高到低第i位是多少。 Total是总
的位数。
◦ 如果我们考虑从高到低位不断填数y_1 y_2 …。那么问题其实就是问有多
少填数的方案,一要满足上限的限制(对应区间[1,n]),二要满足题目
的其他限制。
◦ 这样其实就比[1,n]看起来更能dp了。
◦假设到了第k位y_k!=x_k,则k位之后就没有上限的限制了,情况就简化了。
◦如果前面y中没有出现3:那么假如我们可以求出来, f[k][0]表示k位之后没
有上限限制(随意填),但是必须填个3(前面没有出现),有多少种填
数的方案。
◦如果前面y中出现了3:那么假如我们可以求出来, f[k][1]表示k位之后没有
上限限制(随意填),没有必须出现3的限制(前面出现过了),有多少
种填数的方案。
◦首先我们可以枚举到哪一位y_k!=x_k,然后再枚举这一位是多少,把对应
的F加起来就是答案了,一共需要加 位数*10 次。这运算次数是不大的。
◦而f数组总大小也很小, 位数*2。
◦边界 f[total+1][0]=0,f[total+1][1]=1,转移复杂度O(10)
◦总复杂度
◦那回归到原题呢?
◦枚举哪一位不同没什么变化吧,跟原先一样枚举就好了。
◦就是f数组要变,因为约束条件更多了,所以状态的维数要增加。
◦设f[k][前面是否已经出现13][上一位是否是1][前面的那些数mod13等于多
少],转移的话同样还是枚举这一位是填什么即可。
>那这些是不是大家也都会做了呢!
◦写写方程捋一下思路。
◦ 1:统计区间 [m,n] 中4和7不能同时出现的数字有多少个?
◦ 2:给你一个区间[n,m],要你求区间内不含“62”或“4”的数字的个数?
◦ 3: bzoj1026: windy定义了一种windy数。 不含前导零且相邻两个数字
之差至少为2的正整数被称为windy数。 windy想知道,在A和B之间,包
括A和B,总共有多少个windy数?
>记忆化搜索来实现
◦其实,我们刚刚那个dp的过程自然是思路很清晰,但是一般实现我们不
那么写。因为毕竟还是好多个for循环,预处理一套循环,算答案一套循
环,记忆话搜索的话则是要什么算什么,会好写很多。
◦实际上数位dp往往都是用记忆化搜索的方式来实现,就是求什么调用什
么,调用完了,记下来,下次就不用重新算了。
◦我们来看一下上一题的代码
◦ i 表示到了第几位。
◦ State:上一位是否为1。
◦ Have:是否已经有13.
◦ K:已经加上的数mod13的值
◦注意只有不顶到上界才能记忆化下来答案。
关于数位dp的经验
◦ 1:注意很多时候带进去是n==0要特殊处理。
◦ 2:还有一般问[m,n],我们求[1,n]-[1,m-1]但是有的时候m为0就炸了。 然
后一道题wa一个小时。。。。。。正常。。。。。
◦ 3:求所有包含49的数,其实就是(总数-所有不包含49的数)。前者的化
需要有两维限制,一个是上一位是什么,一个是之前有没有49。但是后
者只需要记一个上一位是什么。就能好写一些。
◦ 4:一般问题的数位dp部分,都是套路,但是这并不代表它外面“华丽的
外衣”和与其他算法结合的的部分也是无脑的。 要看出它是考数位dp,
要看出问题怎么变化一下就是数位dp了。
◦ 5: dp初始化memset要置为-1。不能置为0!!!!!!因为有很多时候
dp值就应该是0,然后我们如果误以为是因为之前没有计算,从新计算的
话,就会tle。
◦这里不能写成0。
◦ 6:既然是记忆化搜索,那就可以剪枝!!!!可行性剪枝!!
◦ 7:注意windy数的情况,有时前导0也需要记的!!!
>hdu3079
◦题中平衡数的定义: 以一个位置作为平衡轴,然后左右其他数字本身大
小作为重量,到平衡轴的距离作为权值,实现左右平衡(即杠杆原理平
衡)。然后为区间[x,y]内平衡数的个数。 (0 ≤ x ≤ y ≤ 1018)
>Solution
◦数位dp当然可以和各种算法套起来。
◦这题就是典型的 枚举+数位dp。
◦首先,感觉这道题如果记录 当前位、选没选平衡轴、当前左边平衡杆权
值减右边平衡杆权值,这样感觉并不好转移。而分析题目性质可以发现,
一个非0数只能会有一个(一一对应) 平衡轴(0除外,最后特殊处理一
下就好),那么如果数位dp外面枚举平衡轴的话,只需计算到最后差是
否为0就好。因为每一种中心轴对应的合法集合互不相交
◦注意0的特殊情况:就是0被统计了位数次,减掉即可。
>bzoj3209 二进制数位dp
◦设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问
你派(Sum(i)),也就是 sum(1)—sum(N) 的乘积。答案对一个质数取模。
◦对于 100% 的数据, N≤10^15
>Solution
◦ 虽然输入的10进制数,但是本质有影响的是二进制形态啊!
◦ 怎么来转换一下,求1~n中每个数的一的个数总相乘之积,首先感觉到,
每个数都会有唯一对应的1的个数,且一的个数的取值不到60,因为n最
大 10^15, 那么我就想,如果枚举1的个数k,计算有多少个数含有k个1,
(因为数位dp就是来做,有多少满足的数,且不关注数的大小)这样就
转化为数位dp的模型了另外,发现含有k个1的数个数可能非常多,快速
幂搞一搞啦。
◦ 这题的关键就是发现一的个数的情况比较少可以枚举再转化为另一种情
况计算其实,这题本质就是转化一下,注意在模型难以建立的情况下,
通过转化,可以将题目简化
>bzoj4521: [Cqoi2016]手机号码
◦数字L到R中有多少个数字满足以下两个条件。
◦ 1:要出现至少3个相邻的相同数字
◦ 2:号码中不能同时出现8和4。
◦ 10^10 < = L < = R < 10^11
>Solution
◦放松题。
◦ dfs(i, same, last, appear, occur8, occur4, limit)
◦ Same:上一位和上上一位是否相同
◦ Last:上一位数字
◦ Appear:连续三个相同是否出现过。
◦ Occur4: 4是否出现过?
◦ Occur8: 8是否出现过?
◦还用记前导0吗?
总结