0/1背包
给出n个物品,每个物品有Vi的价值和Wi的费用,我们总共有m块钱,求最多能得到多少价值的物品。
N<=10^3,m<=10^3
记录方案数?记录输出方案?
输出方案:
对每个dp[i][j]记录是由哪个状态转移过来的,然后从最后一直往前找,输出;
最优策略方案数:再定义一个数组f[i][j]=dp[i-1][j]>dp[i-1][j-w[i]]+v[i]?f[i-1][j]:f[i-1][j-w[i]];
if(dp[i-1][j]==dp[i-1][j-w[i]]+v[i]) f[i][j]= f[i-1][j]+f[i-1][j-w[i]];
完全背包
每一个物品可以选无限个。
dp[i][j]=max{dp[i][j-w[i]],dp[i-1][j]}
多重背包:
对每一个物品,最多能用t[i]次。
最暴力的方法就是转移的时候枚举这个物品选几个即可。
dp[i][j] = max{ dp[i-1][j-w[i]*k] + v[i]*k | k<=t[i]}
复杂度O( N*M*t[i] )
优化!
1.二进制拆分
可以把t[i]拆成1+2+4+8…2^k+x, 这样k+1组, x小于2^(k+1) ,然后我们会发现这些组能拼成0…t[i]每一种情况,然后这样我们就成了n*log(t[i])个物品的0/1背包问题。
复杂度O(n*log(t[i])*m)
2.单调队列优化:
不会
dp[i][j]=dp[i-1][j-w[i]*k]+v[i]*k k<=t[i];
fr[i][j]=dp[i-1][j*w[i]+r];
fr[i][j]=max{fr[i-1][j-k]+v[i]*k|0<=k<=t[i]};
fr[i][j]=max{fr[i-1][p]+v[i]*(j-p)|p>=j-t[i]};
fr[i][j]=max{fr[i-1][p]-v[i]*p|p>=j-t[i]}+v[i]*j;
然后对于j增加1,p的取值上届下届同时增大了1,也就像滑动窗口。
(但还是不会写)
单调队列均摊是O(m)
总复杂度就是O(n*m)的了。
分组背包:
一共有n组,每组有size[i]个物品,第i组第j个物品的费用为w[i][j],价值v[i][j],每个组里的物品是互斥的,意味着你在一组物品中只能选择一个物品,求花费小于等于m能得到的最大价值。
二维背包
给出n个化学反应,每个反应消耗a[i]升氧气和b[i]升氢气,可以得到w[i]的价值,现在总共有X升氧气和Y升氢气,求我们最多可以得到多少价值。
此外还有二维背包问题,实质上和本身和普通背包毫无思维上的区别。
因为限制条件多了一个,所以我们需要给最初最基本的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:
一个残忍的事实,没有夫妻可以住在一起或者只有一对夫妻住在一起;
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]);
然后要注意,在写代码时f[i-1,j-1,k-1,0]+p[i]需要先保证c>=1,j-1,k-1>=0,v[i]>=2;
其余涉及-v[i]的,都要将其值与0取max(因为当你人<0了,显然所有人就都住下了,这时候也算是i个房间j个男人k个女人的一种方案,因此要取max(0,*-v[i]);
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cstdlib> using namespace std; inline int read(){ int ans=0; char last=' ',ch=getchar(); while(ch>'9'||ch<'0') last=ch,ch=getchar(); while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar(); if(last=='-') ans=-ans; return ans; } int m,ff,r,c,ans; int v[310],p[310]; int f[310][310][310][2]; int main(){ m=read();ff=read(); r=read();c=read(); for(int i=1;i<=r;i++){ v[i]=read(); p[i]=read(); } memset(f,0x3f,sizeof(f)); f[0][0][0][0]=0; f[0][0][0][1]=0; for(int i=1;i<=r;i++){ for(int j=0;j<=m;j++){ for(int k=0;k<=ff;k++){ f[i][j][k][0]=f[i-1][j][k][0]; f[i][j][k][1]=f[i-1][j][k][1]; f[i][j][k][0]=min(f[i][j][k][0],f[i-1][max(0,j-v[i])][k][0]+p[i]); f[i][j][k][0]=min(f[i][j][k][0],f[i-1][j][max(0,k-v[i])][0]+p[i]); f[i][j][k][1]=min(f[i][j][k][1],f[i-1][max(0,j-v[i])][k][1]+p[i]); f[i][j][k][1]=min(f[i][j][k][1],f[i-1][j][max(0,k-v[i])][1]+p[i]); if(j-1>=0&&k-1>=0&&c>=1&&v[i]>=2) f[i][j][k][1]=min(f[i][j][k][1],f[i-1][j-1][k-1][0]+p[i]); } } } if(c == 0) ans = f[r][m][ff][0]; else ans=min(f[r][m][ff][1],f[r][m][ff][0]); if(ans == 0x3f3f3f3f) printf("Impossible "); else printf("%d",ans); return 0; }
一类牛逼的套路题
Bzoj 1190
给你N颗宝石,每颗宝石都有重量和价值V。要你从这些宝石中选取一些
宝石,保证总重量不超过W,且总价值最大为,并输出最大的总价值,每
颗宝石的重量符合a*2^b。
V<=1e9
a<=10; b<=30
按照b从大到小排序,先处理b大的,再处理b小的;
把总重量W表示为一个二进制数M,对应f[i][j]每一位
从高到低,定义f[i][j]表示(从b最大的到b最小的)前i个物品,剩下j*2^i的重量,最大价值是多少;
然后当我们选择某个物品时,转移有两种:1.b=i的物品还有,那么转移到f[i][j-ai]=f[i][j]+vi;
2.没有了,那么我们需要向下一位转移。剩余重量从原来的j*2^i=>k*2^(i-1),为保证剩余重量不变,我们需要让k=j*2
然后不知道为什么,还要加上我们上面总重量二进制位的0/1才行?;
bzoj3163 heoi2013 新背包问题
N个物品,第i个物品有c[i]个,购买第i个物品需要a[i]元,可获利b[i]的价
值。有m个询问,每次询问:如果第x个物品禁止购买,你有y元的话,能
获得的最大价值是多少?询问之间互相独立。
N<=1000,m<=3*10^5。
初始一空背包;
dd(l,r) 记录的是除去[l,r]外的物品的构成的背包数组。
初始调用dd(1,n);
然后dd(l,mid)时,把[mid+1,r]内的物品加入dp数组(O(n*数量));
我们这里定义的加入这个物品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)
code:
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
设f[x]表示全部物品都可用恰好装满x体积时的方案数,可以用01背包算法求出。这是总方案数。
然后考虑不选某物品的情况。
设g[x]为不选当前物品恰好装满x体积时的方案数。
当x小于w[i]时, i物品一定不会被选上 g[i]=f[i]。
当x大于等于w[i]时, i物品可能会被选上,直接求不选的情况比较困难。
总方案数及f[x],不选的方案数可以想为先不选i再最后把i选上,即g[x-w[i]]。
解释:因为f[x]的结果有两种可能,一种是不选择第i个物品,那么对应的g就是g[x];
而如果选择了第i个物品,那么我们可以知道,当前物品i不被选恰好装满x-w[i]的体积的方案数是g[x-w[i]],然后因为最后确定了装第i个物品,所以只有一种方案,也就是1*g[x-w[i]];
所以g[x]=f[x]-g[x-w[i]], x从小到大枚举计算g即可。
每次都是线性复杂度,一共n次计算,总复杂度是O(n*m)
可行性的背包问题:
◦给出n个物品,每个物品有体积v[i],要求把物品分成两堆,然后使得两堆物品体积的绝对值最小。n,v[i]<=100
◦我们设总体积为sum,那么我们必定会存在一堆体积和是小于等于sum/2的,然后就是要求去选出一些物品总体积小于等于sum/2,最大能取到的体积是多少。
◦这就是经典的背包问题了。
一类整数划分问题:
1:求把n划分成k个正整数的方案数?
暴力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)。
神仙dp:
dp[i][j]表示把i划分成j个数的方案数;
dp[i][j]=dp[i-j][j]+dp[i-1][j-1];
dp[i-j][j]:没有1,我们可以把最下一层消掉;
dp[i-1][j-1],有1,把1搞掉
2:求把n划分成互不相同k个正整数的方案数?
暴力dp:
大同小异。
暴力dp:设dp[i][j][sum]前i个数,选了j个数,和为sum的方案数是多少,答
案就是dp[n][k][n],考虑下一个数选不选来转移即可。
本质是个0/1背包, 复杂度O(n*k*n)
神仙dp:
设dp[i][j]表示把i划分成j个数的方案数。
dp[i][j]=dp[i-j][j]+dp[i-j][j-1]
这里的有1的方案与上一题不同这里第一维是[i-j],因为要限制没有相同的数字。
保证不会互不相同;O(n√n)
3:求把n划分成k个不大于m的互不相同正整数的方案数?
dp[i][j]=dp[i-j][j]+dp[i-j][j-1]-dp[i-(m+1)][j-1];
对于可能超过m的情况,显然我们可以只看不超过m的那一部分的方案数,因为一层一层的加,所以只会更新到m+1,因此不超过那一部分的方案数为dp[i-(m+1)][j-1],这样将不合法部分减去就是合法答案,然后注意可能会有负数情况哦!
4:求把n划分成k个奇数的方案数?
………………………………………………
BZOJ 3612
给定一个杠杆,等距均匀分布一共2n+1个等重质点,支点在n+1处,求拿走k个质点后使杠杆仍然保持平衡的方案数 mod p的值。
1 <= n <= 10000, 1 <= k <= 10, 2 <= p <= 10000,且 k <= 2n+1
其实就是-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)。
可重复数字纯奇纯偶划分
◦ 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];
◦不可重复也是一样的。