背包问题之完全背包
背包问题相信大家都不陌生,以前我接触过的背包问题是主要是0-1背包以及普通背包问题。
今天,介绍一种也是比较基本的类型——完全背包。
一、完全背包
题目引入
设有物品N种,其重量为Weight[N],价值为Value[N]。现有容量为M的背包,若这N种物品各有无限多个,求该背包可装物品的最大价值为多少?
设dp[N][M],代表前N种物品在容量为M的背包中可产生的最大价值。
若题目没有 “若这N种物品各有无限多个” 这一条件,该问题就转化为了简单的0-1背包问题,在这种情况下:
dp[i][j] = max( dp[i-1][j],dp[i-1][j-Weight[i]]+Value[i] ),
即此时dp[i][j]要么由不放入第i种物品产生,要么放入第i种物品产生,求得两种情况的最大值即可;
但对于题目所给的条件,我们需要考虑第i种物品被放入了几件,故在这种情况下:
dp[i][j] = max( dp[i-1][j],dp[i-1][j-k * Weight[i]] +k * Value[i] ),
根据该动规方程易得循环
for( i=1; i<=N; i++)
for( j=1; j<=M; j++)
for(k=1;k*Weight[i]<=j;k++)
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*Weight[i]]+k*Value[i]);
}
代码优化(降维)
通过观察该方程,我们可以发现,dp[i][j]只与本行在其之前的以及其正上方的元素值有关。因此可向一维数组进行转化。
设f[j]为容量为j的背包可装的最大价值,最后一次装入背包的是weight[i],有:
f[j] = max( f[j], f[j−Weight[i]]+Value[i] ),
因此可得简化后的代码
for( i=1; i<=N; i++)
for( j=1; j<=M; j++)
{
f[j] = max( f[j], f[j−Weight[i]]+Value[i] );
}二、变式——自然数拆分的方案数
(补充:本题中6=6是合法的)
本题目有多种解法,具体使用哪一种要根据N的范围来确定。如回溯法(1——50左右)等,代码放在文章最后。这里以完全背包的解法为例。
完全背包解法
设dp[i][j]为值为j的数,被拆分成1~i的方案和。
进行代码优化,dp[j]代表数j的分解方式的个数。容量为j的背包,每次放入体积为i的物品:
for( i=1; i<=N; i++)
for( j=1; j<=N; j++)
{
//dp[j]+=dp[j−Weight[i]];
dp[j] = dp[j] + dp[j−i];//Weight[i]==i
}以n=3为例,三轮循环结束后,可以得到dp[3]=dp[2]+dp[1]+dp[0].
完整代码如下:
#include<stdio.h>
#include <string.h>
#define maxn 4005
const int mod=2147483648;
long long dp[maxn];
int num,n;
int main()
{
scanf("%d",&num);
while(num--)
{
memset(dp,0,sizeof(dp));
dp[0]=1;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
dp[j]=(dp[j]+dp[j-i])%mod;
}
}
printf("%lld\n",dp[n]%mod );
}
return 0;
}学习参考:https://blog.csdn.net/nuist_Jiang/article/details/86594323
补充:回溯法
#include<stdio.h>
int n;
int x[55]={0};
int c;
void backtrace(int t)
{
if(c==n)
{
printf("%d=%d",n,x[1]);
for(int i=2;x[i]!=0;i++)
{
printf("+%d",x[i]);
}
printf("\n");
}
else
{
for(int i=1;i<=n-t+1;i++)
{
if(c+i<=n&&i>=x[t-1])
{
x[t]=i;
c+=i;
backtrace(t+1);
x[t]=0;
c-=i;
}
//printf("%d\n",t);
}
}
}
int main()
{
scanf("%d",&n);
c=0;
backtrace(1);
return 0;
}
查看18道真题和解析
基恩士成长空间 419人发布