【算法笔记】背包DP①(0/1背包)

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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值