DP问题从入门到精通1(背包问题)
Posted 芜湖之肌肉金轮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DP问题从入门到精通1(背包问题)相关的知识,希望对你有一定的参考价值。
最近写了很多dp的题目,状态方程只要转换出来,就特别简单,所以特意去研究了一下,怎么去做dp的题目,一下是一些见解。
背包问题
背包问题是dp问题的常客了,一般来说只要能将状态方程转换出来,就很好写。那么怎么样才能很快速的写出状态方程呢?emmm貌似没有什么好办法哈哈,多写就好了,只要把dp的题目类型都见一遍,那么状态方程就不是什么难事了。
01背包
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
上面这个就是很经典的01背包,那么首先状态方程是啥,状态方程其实就是我们怎么看他,怎么去分析他的过程,这个二维的状态方程事f[i][j],意思是从前i个物品中选,体积不超过j的价值,根据题目意思,故该方程的属性事max,所以是从前i个物品中选,体积不超过j的最大价值。
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(j>=v[i])
f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
完全背包
有 NN 种物品和一个容量是 VV 的背包,每种物品都有无限件可用。
第 ii 种物品的体积是 vivi,价值是 wiwi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
f[i][j]=f[i-1][j];
if(j>=v[i])f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
}
我们可以发现完全背包问题和01背包问题代码只相差一点点一个是max:f[i-1][j-v[i]]+w[i],一个是max:f[i][j-v[i]]+w[i],为什么呢,这里面其实有个转换。
这个图是从acwing上找的:完全背包(顺便y总yyds)。通过这个转换,我们就很清晰的知道了为什么是f[i][j-v[i]]+w[i]。然后根据我们的状态表示从前i个物品中选,体积不超过j的最大价值,故最后答案就是f[n][m](假设n个物体,最大体积是m)
多重背包
多重背包的题目如果数据较小是可以直接o(n^3)暴力去求解的,状态表示还依旧是从前i个物品中选,体积不超过j的最大价值。所以状态方程就是f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
for(int i=1;i<=n;i++)//枚举所有物品
for(int j=0;j<=m;j++)
{
f[i][j]=f[i-1][j];
for(int k=0;k<=s[i]&&k*v[i]<=j;k++)//枚举拿一个,拿两个....的情况
{
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
但是这个是暴力的解法,假如数据很大呢?比如数据到了1000?,那么10^9肯定会超时。这时候我们可能回想能不能优化成01背包?把每一个三份物体全部看作是三个一份的物体,然后用01背包的思路去做是不是更好?但是一个一个的分实在是太慢了,除了一份一份的分从达到表达原来物体份数的方法,还有没有别的更好的呢?这个时候就要用到背包问题的二进制优化方法:
比如随便一个数字:15,我们可不可以用最少的数字去表示,答案是可以找到的,我们可以用1,2,4,8去表示从0~15所有的数:0一个都不选,1选1,2选2,3选1+2,4选4,5选......14选2+4+8,15选1+2+4+8。如果折射到一般情况的话就是:
int n;//定义任意一个自然数
vector<int> f
cin>>n;
int N=n;
for(int j=1;j<=n;j*=2)
{
n-=j;
f.push_back(j)
}
if(n>0)f.push_back(n);//假如s大于0说明,说明现在数组的数可以组成N-n的整数,所以要加上剩下的n
这样我们就把拆分成份的时间复杂度优化成了o(logn)。
我们已经通过二进制把物体都拆成了只能选一次的各个子物体,这样子就可以直接用01背包去做就可以了
/*
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
*/
const int N = 2010;
vector<pair<int,int >> vw;
int n,m;
int v,w,s;
int f[N];
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>v>>w>>s;
for(int j=1;j<=s;j*=2)
{
s-=j;
vw.push_back({j*v,j*w});
}
if(s>0)vw.push_back({s*v,s*w});
}
for(int i=1;i<=vw.size();i++)
{
for(int j=m;j>=0;j--)
{
auto t=vw[i-1];
f[j]=f[j];
if(j>=t.first)f[j]=max(f[j],f[j-t.first]+t.second);
}
}
cout<<f[m];
return 0;
}
分组背包
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值
经过以上的各种背包问题的洗礼,这个就比较容易想到这个的状态表示方程(才不是因为每个背包问题的状态表示都一样...)从前i组(注意这里是不同的这里是“组”)物品中选,体积不超过j的最大价值。
剩下的就和之前01背包一样了:
/*
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
*/
const int N = 110;
int f[N];
int v[N][N],w[N][N],s[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s[i];
for(int j=1;j<=s[i];j++)
{
cin>>v[i][j]>>w[i][j];
}
}
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k=1;k<=s[i];k++)
{
if(j>=v[i][k])f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
}
cout<<f[m];
return 0;
}
背包类dp结束
(以上就是本菜鸡学习下来的一点点心得体会吧,希望能帮到大家---y总的思路是真的好www)
学习网站acwing
以上是关于DP问题从入门到精通1(背包问题)的主要内容,如果未能解决你的问题,请参考以下文章