这篇博客先说一道洛谷蓝题(实际难度其实可能也就是在橙题左右,难度不大,请放心食用)
1.背包问题的第k优解
首先,我们知道背包问题的最优解,我们可以通过状态转移方程来求出最优解
状态转移方程(滚动数组):dp [ j ]=max(dp [ j ],dp [ j - w [ i ] ] + v [ i ] );
但是如果我们要去求一个k优解,那么首先我们需要再多一维数组去来记录我们我们的第 k 优解
所以dp数组为 dp[ j ] [ k ],表示对于容量为 j 的背包,第 k 最优解为 dp[ j ] [ k ]
我们正常的最优解是通过两个状态转移而来,但是我们对于前k个解来说,我们相当于通过两个队列得出我们的前k个解,相当于合并队列的思想,我们这个队列是从大到小排的
我们的 dp [ j ] [ k ]可以来从两个队列中得到,一个是dp [ j ] [ k] 还有一个是 dp [ j -w[ i ] ] [ k]+v[ i ];
总的时间复杂度为O(N*V*K)
为什么这个方法正确呢?实际上,一个正确的状态转移方程的求解过程遍历了所有可用的策略,也就覆盖了问题的所有方案。只不过由于是求最优解,所以其它在任何一个策略上达不到最优的方案都被忽略了。如果把每个状态表示成一个大小为K的数组,并在这个数组中有序的保存该状态可取到的前K个最优值。那么,对于任两个状态的max运算等价于两个由大到小的有序队列的合并。
另外还要注意题目对于“第K优解”的定义,将策略不同但权值相同的两个方案是看作同一个解还是不同的解。如果是前者,则维护有序队列时要保证队列里的数没有重复的。
传送门——P1858 多人背包
就是我上面阐述内容的一个小例题,很简单,放心食用,我写了一部分注释,看不懂私信即可
//多人背包
#include<bits/stdc++.h>
using namespace std;
int k,v1,n;
int w[205];//重量
int v[205];//价值
int dp[5005][55];
int t[55];//一个临时数组,两个队列找出的前k个最大的
int main()
{
cin>>k>>v1>>n;
for(int i=1;i<=n;i++)
{
cin>>w[i]>>v[i];
}
memset(dp,-0x3f3f3f3f,sizeof(dp));//因为要把背包装满的最大值,所以我们的非法状态要设为负的最大值
dp[0][1]=0;
for(int i=1;i<=n;i++)
{
for(int j=v1;j>=w[i];j--)
{
int c1=1,c2=1,cnt=0;//cnt记录更新的总数 ,c1是第一个队列的轮到那个,c2是第二个队列轮到哪个
while(cnt<=k)
{
if(dp[j][c1]>dp[j-w[i]][c2]+v[i])
{
cnt++;
t[cnt]=dp[j][c1++];
}
else
{
cnt++;
t[cnt]=dp[j-w[i]][c2++]+v[i];
}
}
for(int z=1;z<=k;z++)
dp[j][z]=t[z];
}
}
int ans=0;
for(int i=1;i<=k;i++)
ans+=dp[v1][i];
cout<<ans;
return 0;
}