问题描述:现在有一个背包,它的体积为C,现在有N种物品(每个物品只有一个),每个物品的价值W[i]和占用空间C[i]都会由输入给出,现在问这个背包最多能携带总价值多少的物品?
对于第i件物品,只有装和不装俩种选择。乍一看,dfs?
#include<stdio.h>
int v[10000],w[10000];
int ans;
int temp;
void dfs(int n,int c)
{
if(n==0)
{
if(temp>ans) ans=temp;
return;
}
if(c>=w[n])
{
temp+=v[n];
dfs(n-1,c-w[n]);
temp-=v[n];
}
dfs(n-1,c);
}
int main()
{
int n,c;
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++)
{
scanf("%d",&v[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%d",&w[i]);
}
dfs(n,c);
printf("%d",ans);
return 0;
}
问题解决了,溜了溜了。
动态规划通常用于最优解问题,
- 与递归分治法类似,都是将大问题拆分成小问题,通过寻找大问题和小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。
- 不同的是,递归分治法如果要同时求大问题和其子问题,就会被重复计算。而动态规划具有记忆性,通过填表把所有已经解决的子问题的答案记录下来,在新问题需要用到子问题时直接提取,避免重复计算。
核心:填表
难点:状态转移方程
通过上面的dfs可以发现:
dfs(n,c)=
dfs(n-1,c) wn>C(放不下)
max{dfs(n-1,c),dfs(n-1,c-wn)+vn}
我们通过二维数组dp[i][j],来记忆物品数量i,背包容量j的答案。
得到状态转移方程:
dp[i][j]=
dp[i-1][j] wi>j
max{dp[i-1][j],dp[i-1][j-wi]+vi} j>wi
代码实现
#include<bits/stdc++.h>
using namespace std;
int v[10000],w[10000];
int dp[10000][10000];
int main()
{
int n,c;
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++)
{
scanf("%d",&v[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%d",&w[i]);
}
for(int i=1;i<=n;i++)//填表
{
for(int j=1;j<=c;j++)
{
if(w[i]>j)
{
dp[i][j]=dp[i-1][j];
}
else
{
dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
}
}
}
printf("%d",dp[n][c]);
return 0;
}
- i=0与j=0那两行赋初值为0.
空间优化
不难发现,dp[i][...]只与dp[i-1][...]有关。所有可以将dp数组降维。
#include<bits/stdc++.h>
using namespace std;
int v[10000],w[10000];
int dp[10000];
int main()
{
int n,c;
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++)
{
scanf("%d",&v[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%d",&w[i]);
}
for(int i=1;i<=n;i++)//填表
{
for(int j=c;j>=0;j--)//j必须从后面开始,这样dp[j]的改变才不会影响前面的dp
{
if(j>=w[i]&&dp[j-w[i]]+v[i]>dp[j])
{
dp[j]=dp[j-w[i]]+v[i];
}
}
}
printf("%d",dp[c]);
return 0;
}
dp[0]初值为0。
这样做的缺点就是牺牲了大量数据,如果刚好要求最后的答案 ,那没什么。
P1048 [NOIP2005 普及组] 采药https://www.luogu.com.cn/problem/P1048