1.0/1背包(参考例题:HLOJ416采药)
二维解法:
我们设f[i][j]为前i个物品放进容量为j的背包的最大价值
设体积为v[i],价值为w[i]:
我们可以枚举i(1到n)和j(1到n),不难得出状态转移方程
f[i][j]=max{f[i-1][j],f[i-1][j-v[i]]+w[i]}
可以知道,当第i件物品不取时,总价值为f[i-1][j]
取得话,总价值为前i-1件物品j-v[i]大小的最大值,j-v[i]是为了给第i件物品腾出空间来访,加上第i件物品的价值,所以是f[i-1][j-v[i]]+w[i]
代码如下:
for (int i=1;i<=n;i++)
for(int v=1;v<=t;v++)
{
f[i][v]=f[i-1][v];
if (v-c[i]>=0)
if (f[i-1][v-c[i]]+w[i]>f[i][v])
f[i][v]=f[i-1][v-c[i]]+w[i];
}
一维解法:
可以知道,每一层i只被覆盖一次就会浪费,会造成大量空间浪费,因此我们要进行一维数组的优化。
假如我们直接把二维数组去掉,会变成这个样子:
for (int i=1;i<=n;i++)
for(int v=1;v<=t;v++)
{
f[v]=f[v];
if (v-c[i]>=0)
if (f[v-c[i]]+w[i]>[v])
f[v]=f[v-c[i]]+w[i];
}
因为我们把二维数组去掉,原来是:前i件物品j个容量的背包
而现在是:j容量的背包放进的最大物品,前i件是被忽略的,因此这个方程的一部分:
f[v-c[i]]最优值并不是前i-1个物品的最优值,而是前i件物品的最优值,因为在当v(之前的)=v(当前的)-c[i]且i和当前枚举的i相同时,已经用过第i件物品了,所以是完全背包,不是01背包,所以我们可以选择这么做:将v逆序枚举,那么我们就可以确保之前没有被枚举过了。
最后附上01背包(采药)代码:
#include<bits/stdc++.h>//f[i]表示容量为i的背包所装的物品的最大值
using namespace std;
int main()
{
int V,n,w[10000]={},v[10000]={},f[10000]={};
cin>>V>>n;//v表示背包的总容量
for (int i=1;i<=n;i++)
cin>>v[i]>>w[i];//v[i]表示每一个物品的体积,w[i]为价值
for (int i=1;i<=n;i++)//i枚举物品
for (int j=V;j>=v[i];j--)//j枚举体积,到v[i]既可以节约空间复杂度,也可以免去if
f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[V];
}
2.01背包的计数:以HLOJ417集合求和为例:
由题目可知,当n(n+1)%2==1时是无法分解为两个集合的,因此直接判断
当n*(n+1)为偶数的时候,我们就可以把它当成01背包来做
如果是二维:设f[i][j]为前i个数总和为j的集合的方案数,把它想成01背包,可以这么得到方程:
如果i不要:那么方案数是f[i-1][j]
如果i要且可以要:那么方案数数f[i-1][j-i]
那么f[i][j]的方案数总和就是f[i-1][j]+f[i-1][j-i].
那么二维的写法是这样的:
for (int i=1;i<=n;i++)
for (int v=1;v<=ans;v++)
{
f[i][v]=f[i-1][v];
if (v-i>=0) f[i][v]+=f[i-1][v-i];
}
那么其实一维数组的写法也是一样的,自行理解把!
#include<bits/stdc++.h>//f[i]表示总和为i的集合方案数
using namespace std;
int main()
{
long long n,sum,f[10000]={};
cin>>n;
sum=n*(n+1)/2;
if (sum%2==1)
{
cout<<0;
return 0;
}
sum/=2;
f[0]=1;
for (int i=1;i<=n;i++)
for (int j=sum;j>=i;j--)
f[j]+=f[j-i];
cout<<f[sum]/2;
return 0;
}