背包
背包是dp(动态规划)的一类常见题型,题目变幻莫测,就像:
某牛客网and某杭大and还是杭大and某落谷;
算了,我都受不了。
那么,我们就开始细细分析一下背包问题们;↓
01背包
关于01背包,本意是给定N件重量为m[i]、价值为w[i]的物品和承重为V的背包,求在背包承重范围内接受物品的最大价值。
很明显,01背包的特点就是 每件物品只有一件,选择放/1或者不放/0。
那么最重要的是,转移方程:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-m[i]]+w[i]);
没错,一点都不难理解,对于dp[i-1][j],即为不去物品i,后者当然是取物品i并且价值加上w[i]啦
easy
所以?这不重要,关键是我们还可以将状态压缩成一维形态,由于i只回溯到i-1的纵向级别,所以说:
for(int i=1->n)
for(int j=v->1)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
对,我们将i放到for循环中去了,这样我们就节约了极大的空间来支撑其余的储存(一些杂题中,此处不作探讨)。同时因为需要追溯dp[j-v[i]],故而我们让体积由后向前循环。
来一段82年的code压压惊:见题
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int dp[3000100];//每张发票不超过1000,最多30张,扩大100倍数后就要开这么大
int main()
{
char ch;
double x,y;
int sum,a,b,c,money[35],v;
int t,i,j,k;
while (~scanf("%lf%d",&x,&t),t)
{
sum = (int)(x*100);//将小数化作整数处理
memset(money,0,sizeof money);
memset(dp,0,sizeof dp);
int l = 0;
for (i = 0; i<t; i++)
{
scanf("%d",&k);
a = b = c = 0;
int flag = 1;
while (k--)
{
scanf(" %c:%lf",&ch,&y);
v = (int)(y*100);
if (ch == 'A' and a+v <= 60000)
a += v;
else if (ch == 'B' and b+v <= 60000)
b += v;
else if (ch == 'C' and c+v <= 60000)
c += v;
else
flag = 0;
}
if (a+b+c <= 100000 and a <= 60000 and b <= 60000 and c <= 60000 and flag)//已知必须满足这些条件
money[l++] = a+b+c;
}
for (i = 0; i <= l; i++)
{
for (j = sum; j >= money[i]; j--)
dp[j] = max(dp[j],dp[j-money[i]]+money[i]);
}
printf("%.2lf\n",dp[sum]/100.0);
}
return 0;
}
Ps:这是很浅显的一种变式
完全背包
本意~~(基本大意)~~ 就是给定N种重量为m[i]、价值为w[i]的物品和承重为V的背包,求在背包承重范围内接受物品的最大价值。
很明显,这里多出了一个信息,那就是每种物品可以有若干件,也就是说,我们甚至可以无限制地去取一样单位空间最贵的物品,但是难免会碰到如下情况:
2 5 //物品种数及背包空间
5 20 //分别代表物品体积及价值,下同
2 9
是个明眼人都看出来了,上述题目使用贪心必然会失败,所以动规是必不可少的。
重头戏登场:
dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i]);
借用上面的思想,我们同样选择状压,因为本次使用的转移方程也是只涉及到i-1的纵向级别。但与上面不同的是,此次考虑的对象是dp[i][j-v[i]],所以我们必须让体积由前向后循环,正如:
for(int i=1->n)
for(int j=1->v)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
好的,堪称完美。
code以及某落谷上的noip:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int dp[25005];
int f[25005];
int n,t,ans;
int main (){
scanf("%d",&t);
for (int i = 1 ; i <= t ; ++i){
scanf("%d",&n);
memset(dp,0,sizeof dp);
for (int i = 1 ; i <= n ; ++i){
scanf("%d",&f[i]);
}
sort (f + 1 , f + n + 1);
dp[0] = 1 ;//一个也不选方案数为1
for (int i = 1 ; i <= n ; ++i){
if (!dp[f[i]])
++ans ;//如果没有一个方案可以表示这个数,就表示这个数必须被选
for (int j = f[i] ; j <= f[n] ; ++j)
dp[j] += dp[j - f[i]];
}
printf ("%d\n",ans);
}
return 0 ;
}
Ps:实话说博主在参赛时也没有想到它是完全背包,第一反应就是小凯的诱惑,最后暴力也只打了20分惭愧啊 。
话说回来,这个变式是真的严重简单。
多重背包
为什么将完全放在第二个讲呢?
其实就是因为多重它比完全又多了一重限制规定(稍后看官们就知道真正的限制了),那就是这次的N种物品都是有指定数量n[i]的,这是一个非常令人伤心的规定,因为完全背包基本会爆,而转化为01又过于繁琐,那么我们这个时候就应该将01和完全思想结合。
来一道令旁人侧目的传送门&
code:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int main()
{
int T,n,m;
int v[105],w[105],num[105],dp[105];
cin>>T;
while(T--)
{
cin>>m>>n;
for(int i=0;i<n;i++)
cin>>v[i]>>w[i]>>num[i];
memset(dp,0,sizeof(dp));
for(int i=0;i<n;i++)
for(int j=0;j<num[i];j++)
for(int k=m;k>=v[i];k--)
dp[k]=max(dp[k],dp[k-v[i]]+w[i]);
cout<<dp[m]<<endl;
}
return 0;
}
Ps:We are comming.
Now next;↓
双重背包
没听过吧?长见识了吧?孤陋寡闻了吧?
这是一种很诡异的题型,因为他平白无故又多加了一个限制↓
这N件物品不仅有体积,竟然还有重量(虽然这很正常)m[i],在保证背包的V不被撑爆的同时,也要预防你被超过M的物品压shi,并且尽量地使价值总和W最大。
接下来是您的独秀时间……
(好的,我知道你们不会)大家都是dalao,会是必须的啊
讲解时间
这种题目其实在我个人看来,并没有什么比较大的难度,无非就是要开一个三维dp,很明显,就是一个二维dp数组可以搞定的事情(时刻谨记状态压缩节省空间)。比如:
dp[i][j]=max(dp[i][j],dp[i-v[k]][j-m[k]]+w[k]) |
对于这个类型的话,给出转移方程就是一切了,循环之类的大家根据上面的方式也可以推出个大概,所以代码就算了吧 |
但是博主就在准备收手的时候发现了一个惊为天人的情况 是我孤陋寡闻了 比如说:
简直是……
所以博主还要继续编辑树形dp,环型dp,数位dp和多维dp。
emmmm……谁能发现这一玄机呢?
Ps:所以重点是,我还是得先暂停是嘛)