D2
背包DP模型
一般是给出一些“物品”,每个物品具有一些价值参数和花费参数,要求在满足花费限制下最大化价值或者方案数。
最简单几种类型以及模型
0/1背包
完全背包
多重背包
0/1背包
给出n个物品,每个物品有Vi的价值和Wi的费用,我们总共有m块钱,求最多能得到多少价值的物品。
N<=10^3, m<=10^3
我们设dp[i][j]表示到第i个物品花费了j元,所得到的最大价值是什么
设dp[i][j]表示前i个物品,用了j的体积得到的最大的价值。
则dp[i][j]=max{dp[i-1][j] , dp[i-1][j-w[i]]+v[i]}(选或者不选)
复杂度O(N*M)
贴一个一维的(采药)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int T,m,t[110],v[110],qaq[1010];
int main(){
scanf("%d%d",&T,&m);
for(int i=1;i<=m;++i)
scanf("%d%d",&t[i],&v[i]);
qaq[0]=0;
for(int i=1;i<=m;++i)
for(int j=T;j>=t[i];--j)
qaq[j]=max(qaq[j-t[i]]+v[i],qaq[j]);
cout<<qaq[T];
return 0;
}
如果要记录方案数呢?
我们可以再开一个f数组表示方案数,那么f[i][j]就是由dp[i-1][j]和dp[i-1][j-w[i]] + v[i]当中较大的的那个的f,如果两个dp值相同,那么f[i][j]就同时加两个f
如果要求输出方案呢
对于每一个情况,我们记录一下他由哪种情况转移过来(选或者不选),最后一并输出即可
完全背包
每一个物品可以选无限个。
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]}
用个for循环就行
复杂度
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)
用二进制拆分可以证明与多重背包是等价的,首先从拆分方面讲,一定是可以拆完的,又因为拆分后每个组的价值不一样,所以我们可以当做01背包做
求方案数呢?
不可以,当t不被2整除的时候,0-2(k-1)可以被完全计算,2(k-1) - x 这几个数当中,有多种拆分方式,所以有的物品会被扔进去多次,所以结果被重复计算了
单调队列优化
我们来观察一下式子
我们发现对于第二维,我们的j和能转移过来的j-w[i]*k在模w[i]意义下是同余的,也就是说我们可以对于第二维按照模w[i]进行分类,不同类之间不会互相影响。
设f[j]=dp[i-1][j*w[i]+r]。 r是我们枚举模w[i]的一个类。
实际上就是一个滑动窗口取最值的问题,直接单调队列优化即可
这个优化,虽然用单调队列不能求方案数,但是我们可以维护一个前缀和就行了!
分组背包
一共有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
-
给你一些房间,告诉你这些房间的容纳人数和价格。
-
安排一定数量的人住到旅馆里,满足:
a. 不同性别的人如果不是夫妻那么不能住一个房间。
b. 一对夫妻如果住在一起,那么房间不能安排其他的人进去哪怕房间
没盛满人。
你来写一个程序帮助佳佳找到安排这些来参加旅行的人住进旅馆所需要
的最小花费。
M:参加旅行的男性人数、 f:参加旅行的女性人数、 r:旅馆的房间数、c:这些男女中有多少对夫妻、 Bi:每个房子容纳人数和、 Pi:每个房子的价格。
注意每一个人不是单身就是和他/她唯一的妻子/丈夫一起参加旅行。
0<=m, f, r<=300, 0<=c<=Min(m, f), 0<=Pi<=10。 2<=Bi<=300
题解
还是要先明确,对于一道背包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])
一类牛逼的套路图
Bzoj 1190
给你N颗宝石,每颗宝石都有重量和价值V。要你从这些宝石中选取一些宝石,保证总重量不超过W,且总价值最大为,并输出最大的总价值,每颗宝石的重量符合a*2^b。
V<=1e9
a<=10; b<=30
看也看不懂的题解
看到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][j2+零碎部分],注意到n=100,
a<=10,所以剩余重量最多纪录到1000即可。
◦复杂度O(n1000)
几个背包变种和模型
Bzoj 3163 heoi2013 新背包问题
分治加(一点点像线段树)
初始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(nmlog(n)), (此处m不是题目中的m)每个物品被加进去log次,每次O(m)
代码实现
bzoj2287
ftiasch 有 N 个物品, 体积分别是 W1, W2, ..., WN。 由于她的疏忽, 第 i 个品丢失了。 “要使用剩下的 N - 1 物品装满容积为 x 的背包,有几种方法呢?” -- 这是经典的问题了。
◦她把答案记为 Count(i, x) ,想要得到所有1 <= i <= N, 1 <= x <= M的 Count(i,
x) 表格。
整数划分模型
1. 求把n划分成k个正整数的方案数?
求把n划分成k个正整数的方案数:暴力dp
设dp[i][j][sum]前i个数,选了j个数,和为sum的方案数是多少,答案就是dp[n][k][n],考虑这一个数选几个来转移即可。这个状态是nkn的转移O(sum/i)。均摊O(nkn*log(n))复杂度有些高。
本质是个完全背包的,那种写法的话可以做到O(nkn)。
****
求把n划分成互不相同k个正整数的方案数?
求把n划分成k个不大于m的互不相同正整数的方案数?
求把n划分成k个奇数的方案数?
md
数位DP
经典的数位Dp是要求统计符合限制的数字的个数。
一般的形式是:求区间[n, m]满足限制f(1)、 f(2)、 f(3)等等的数字的数量是多少。
条件 f(i) 一般与数的大小无关,而与数的组成有关。
善用不同进制来处理,一般问题都是10进制和二进制的数位dp。
数位dp的部分一般都是很套路的,但是有些题目在数位dp外面套了一个华丽的外衣,有时我们难以看出来。
HDU3652简化版
统计区间 [1, n] 中含有 '3' 的数字有多少个。
hdu 3709
如果sigema(i - mid) * d[i] = 0, 那么这个是个平衡数;
bzoj 3209
设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问
你派(Sum(i)), 也就是 sum(1)—sum(N) 的乘积。答案对一个质数取模。
◦对于 100% 的数据, N≤10^15
◦思路应该很自然吧
虽然输入的10进制数,但是本质有影响的是二进制形态啊!
◦ 怎么来转换一下,求1~n中每个数的一的个数总相乘之积,首先感觉到,每个数都会有唯一对应的1的个数,且一的个数的取值不到60,因为n最大 10^15, 那么我就想,如果枚举1的个数k,计算有多少个数含有k个1,(因为数位dp就是来做,有多少满足的数,且不关注数的大小)这样就转化为数位dp的模型了另外,发现含有k个1的数个数可能非常多,快速幂搞一搞啦。
这题的关键就是发现一的个数的情况比较少可以枚举再转化为另一种情况计算其实,这题本质就是转化一下,注意在模型难以建立的情况下,通过转化,可以将题目简化,
手机号码
用
dp[i][j] = [1/0][[1.0]]
基础树形DP
树上最大独立集(消防局)
树的直径
考虑直径一定是有一个lca,所以我们枚举每一个点,看以这个点作为祖先的最长路径,最后取max就行,然后对于一个点的最长路径,就是他的儿子的最长距离和次长距离拼起来