0-1背包问题
0-1背包指的是给出若干物品的价值和重量,每个物品只有一个,要求用一定的背包容量装物品后的最大价值。
可以先看个表格观察
dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
那么可以有两个方向推出来dp[ i ][ j ],
- 不放物品i:由dp[ i - 1][ j ]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)
- 放物品i:由dp[ i - 1][ j - weight[ i ] ]推出,dp[ i - 1][ j - weight[ i ] ] 为背包容量为j - weight[ i ]的时候不放物品i的最大价值,那么dp[i - 1][ j - weight[ i ] ] + value[ i ] (物品i的价值),就是背包放物品i得到的最大价值
由此可以得到状态转移方程:dp[ i ][ j ]=max( dp[ i -1 ] [ j ],v[ i ] + dp[ i -1 ] [ j - w[ i ] ] )
代码实现如下:
#include<stdio.h>
int w[110],v[110],dp[110][20100];//w[i]记录每件物品的重量,v[i]记录每件物品的价值
//dp[i][j]=max(dp[i-1][j],v[i]+dp[i-1][j-w[i]]);动态转移方程
int maxweight;//背包容量
int main()
{
int n=0;
scanf("%d%d",&maxweight,&n);//背包容量,物品数量
for(int i=1; i<=n; i++)
scanf("%d%d",&w[i],&v[i]);
for(int i=1;i<=n;i++)
{
//讨论每个物品在容量1到n时的最大价值
for(int j=1;j<=maxweight;j++)
{
if(w[i]<=j)
{
dp[i][j]=dp[i-1][j]>(v[i]+dp[i-1][j-w[i]])?dp[i-1][j]:(v[i]+dp[i-1][j-w[i]]);
}
else//背包放不下
dp[i][j]=dp[i-1][j];
}
}
printf("%d",dp[n][maxweight]);
return 0;
}
其实你仔细想就可以发现,dp数组还能进行空间上的压缩。即可用个一维数组来表示,因为状态转移,我只要保留前一种状态就可以了。
dp一维数组,下标表示容量,值表示最大价值,dp[i]表示容量为i时背包所装的最大价值。
有一个物品时,dp数组情况如下:
有两个物品时,当你的容量为4时,你正着循环就会出错。如下图:
因为你需要dp[i-1][j-2],在这里即为dp[1][2],但是它已经被新的值覆盖掉了。为什么呢,因为j-w[i]<j,你想要的dp[i-1][j-w[i]]总是会在前面就被覆盖掉,所以我们得从后面开始循环。如下:
重点:倒过来循环递推
dp[j]:相当于dp[i-1][j]
dp[j-w[i]]:相当于dp[i-1][j-w[i]]
#include<stdio.h>
int dp[20100];
int main()
{
int maxweight=0,n=0,wi=0,vi=0;
scanf("%d%d",&maxweight,&n);//背包容量,物品数量
for(int i=1;i<=n;i++)
{
scanf("%d%d",&wi,&vi);//物品重量,物品价值
for(int j=maxweight;j>=wi;j--)
{
dp[j]=dp[j]>(dp[j-wi]+vi)?dp[j]:(dp[j-wi]+vi);//取最大值
}
}
printf("%d",dp[maxweight]);
return 0;
}
言归正传,回到开头的问题。
因为kkksc03是双核,故单科最少总时间分配给两核的时间应尽量相同,将每科总时长的1/2记为背包容量,使用0-1背包思想解题,再用该科总时长-dp[总时长的1/2]即为该科的最少时间。
实现代码如下:
#include<stdio.h>
int num[5],test[21],dp[1000],ans;
int main()
{
for(int i=1; i<=4; i++)
{
scanf("%d",&num[i]);
}
for(int i=1; i<=4; i++)
{
int sum=0;
for(int j=1; j<=num[i]; j++)
{
scanf("%d",&test[j]);
sum+=test[j];
}
for(int j=1; j<=num[i]; j++)
{
for(int k=sum/2; k>=test[j]; k--)
{
dp[k]=dp[k]>(dp[k-test[j]]+test[j])?dp[k]:(dp[k-test[j]]+test[j]);//优化后0-1背包的写法
}
}
ans+=(sum-dp[sum/2]);
for(int j=1; j<=sum/2; j++)dp[j]=0; //非常重要,记得清0
}
printf("%d",ans);
return 0;
}
完全背包问题
完全背包与0-1背包不同的点就在于每个物品有无限多个。,由此我们直接可以得到一个原始状态转移方程。dp[i][j]= max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]) (1 <= k <= w/w[i]).
下面是一系列推导。
最后我们得到的: dp[ i ][ j ]=max(dp[ i -1 ][ j ],dp[ i ][ j -w[ i ] ] +v[ i ])
区别就在这个 i ,完全背包是 dp[ i ][ j -w[ i ] ] ,而 01背包是 dp[ i -1 ][ j - w[ i ] ]
非常有意思的是这个方程与k无关,且与0-1背包的方程非常相似!
当然这里我们也能进行空间上的优化,但与0-1背包不同的是,他是正着来循环的!其他的是一样的!
现在我来敲一敲优化后的代码。
#include<stdio.h>
int dp[10000];
int main()
{
int maxweigh,n,wi,vi;
scanf("%d%d",&maxweigh,&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&wi,&vi);
//正着循环
for(int j=wi;j<=maxweigh;j++)
{
dp[j]=dp[j]>(dp[j-wi]+vi)?dp[j]:(dp[j-wi]+vi);
}
}
printf("%d",dp[maxweigh]);
return 0;
}
多重背包问题
什么叫多重背包呢。题目如下:
多重背包与完全背包不同的是每个物品都有个数量,而不是无限多个。那么如何解决这类问题呢。核心办法就是将多重背包转化为0-1背包。比方说每种物品有si件,那么我们可以把这si件物品一件一件全部存起来,转化为有si件物品,每个物品有一件。
实现代码如下:
#include<stdio.h>
int dp[110],v[10010],w[10010];
int main()
{
int maxweight=0,n=0,wi=0,vi=0,si=0,k=1;
scanf("%d%d",&n,&maxweight);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&vi,&wi,&si);
for(int j=1;j<=si;j++)
{
v[k]=vi;
w[k]=wi;
k++;
}
}
for(int i=1;i<k;i++)
{
for(int j=maxweight;j>=v[i];j--)
{
dp[j]=dp[j]>(dp[j-v[i]]+w[i])?dp[j]:(dp[j-v[i]]+w[i]);
}
}
printf("%d",dp[maxweight]);
return 0;
}
上面的写法虽然好理解但不常用,还有另一种等价常用写法。
#include<stdio.h>
int dp[110],v[110],w[110],s[110];
int main()
{
int maxweight=0,n=0;
scanf("%d%d",&n,&maxweight);
for(int i=1; i<=n; i++)
{
scanf("%d%d%d",&v[i],&w[i],&s[i]);
}
for(int i=1; i<=n; i++)
{
for(int k=1; k<=s[i]; k++)
{
for(int j=maxweight; j>=v[i]; j--)
{
dp[j]=dp[j]>(dp[j-v[i]]+w[i])?dp[j]:(dp[j-v[i]]+w[i]);
}
}
}
printf("%d",dp[maxweight]);
return 0;
}
dp问题解题步骤
- 状态表示。像dp[i][j]
- 状态计算,即推导出状态转移方程
- 状态初始化,由初始化状态一步一步转移