文章目录
- 背包习题
- [278. 数字组合 - (01背包,求方案数)](https://www.acwing.com/problem/content/280/)
- [279. 自然数拆分 - (完全背包,求方案数)](https://www.acwing.com/problem/content/281/)
- [P1060 (01 背包)](https://www.luogu.com.cn/problem/P1060)
- [P1757 ( 分组背包 )](https://www.luogu.com.cn/problem/P1757)
- [280. 陪审团 - (二维费用,求具体方案)](https://www.acwing.com/problem/content/282/)
- [281. 硬币 - 多重背包](https://www.acwing.com/problem/content/283/)
背包习题
建议结合背包九讲(灵魂版)-CSDN博客这篇博客进行学习
278. 数字组合 - (01背包,求方案数)
给出 n n n个数字,组合= m m m,求几种组合方式
思路
这个题对应背包九讲(灵魂版)-CSDN博客中的背包问题求方案数知识。
既然和为 m m m,我们将把体积看作 m m m,结合01背包,这里最优价值,其实就是m.也就是每1体积,价值为1。
所以我们将数组 f f f,赋值f[0]=1,其余=0.为了保证结果是由0递推得出。
因为01背包不同物品的单位体积价值不同,所以需要int t=max(dp[j],dp[j-v[i]]+w[i])
进行比较得出更优。
而这里选择哪个效果都是一样的,只要能组合成 m m m.所以这个题不需要比较。直接用 f [ j ] + = f [ j − v ] f[j]+=f[j-v] f[j]+=f[j−v]
也就是更新累加体积为 j j j时选择该物品的方案数
代码
#include<bits/stdc++.h>
#define fir(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
int n,m,a[105],f[10500];
int main()
{
cin>>n>>m;
f[0]=1;
for(int i=1;i<=n;i++)
cin>>a[i];
fir(i,1,n)
{
for(int j=m;j>=a[i];j--)
f[j]+=f[j-a[i]];
}
cout<<f[m]<<'\n';
}
279. 自然数拆分 - (完全背包,求方案数)
将数字拆分为至少两个正整数相加,求方案数,对结果取模
思路
题目标签是完全背包,再结合求方案数问题,根据上一题代码略加修改即可。
我们需要将** j j j循环变成正序**,即每个数字可以重复考虑,这是完全和01区别。
那
i
i
i循环怎么写?
它表示的是选择的数字,我们选择数字的范围是**[1,m-1]**。
记得开long long,取模
代码
#include<bits/stdc++.h>
#define fir(i,a,b) for(int i=a;i<=b;i++)
#define int long long
using namespace std;
int n,m,f[10500];
signed main()
{
cin>>m;
f[0]=1;
fir(i,1,m-1)
{
for(int j=i;j<=m;j++)
{
f[j]+=f[j-i];
f[j]%=2147483648;
}
}
cout<<f[m]%2147483648<<'\n';
}
P1060 (01 背包)
给定总钱数 n n n,物品总数 m m m.再 n n n范围内,使得 价值 v ∗ 重要程度 w 价值v*重要程度w 价值v∗重要程度w最大。
思路
显而易见的01背包
代码
int dp[30050],v[30],p[30];
signed main()
{
IOS
int n,m;
cin>>n>>m;
fir(i,1,m)
{
cin>>v[i]>>p[i];
for(int j=n;j>=v[i];j--)
{
dp[j]=max(dp[j],dp[j-v[i]]+v[i]*p[i]);
}
}
cout<<dp[n]<<'\n';
return 0;
}
P1757 ( 分组背包 )
思路
和模板题区别就是,需要开个容器去分类存放物品。之后三个循环,进行状态转移。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define fir(i,a,b) for(int i=a;i<=b;i++)
#define PII pair<int,int>
#define fi first
#define se second
const int N=1e3+10;
map<int,vector<PII> > v;
int dp[N];
signed main()
{
IOS
int n,m;
cin>>m>>n;
fir(i,1,n)
{ int a,b,c;
cin>>a>>b>>c;
v[c].push_back({a,b});
}
for(auto it:v)
{
for(int j=m;j>=1;j--)
{ for(auto ww:it.se)
{ int v= ww.fi,w=ww.se;
if(j>=v)
dp[j]=max(dp[j],dp[j-v]+w);
}
}
}
cout<<dp[m]<<'\n';
}
280. 陪审团 - (二维费用,求具体方案)
思路
既要满足m个,还要差值最小,再找到和最大,且求具体方案
为了解决这些问题,
- 我们需要初始化为负无穷。为了保证所得结果一定是差值最小的,而不是由其他更大差值状态转移所得。
- 差值可正可负,因为是总差值,所以中间阶段不能简单的用绝对值。所以整体+400.用 400 + ( d [ i ] − p [ i ] ) = 400 − p [ i ] + d [ i ] 400+(d[i]-p[i])=400-p[i]+d[i] 400+(d[i]−p[i])=400−p[i]+d[i]表示差值,此时范围是【0,800】
- 因为求具体方案,不能降维,三个循环 i , 遍历每一个评分; j , 更新选择不同数量 1 , m ; k , 遍历所有可能差值 i,遍历每一个评分; j,更新选择不同数量1,m; k,遍历所有可能差值 i,遍历每一个评分;j,更新选择不同数量1,m;k,遍历所有可能差值,不断更新dp.
- d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示考虑前 i i i个评分,选取 j j j个,差值为 k k k时的最大和。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=205;
int dp[N][25][810],p[N],d[N];
int main()
{
int n,m,T=1;
while( cin>>n>>m)
{
if(n==0&&m==0) break;
memset(dp,128,sizeof(dp));//初始化无穷小
dp[0][0][400]=0;//初始化为
for(int i=1;i<=n;i++)
{
cin>>p[i]>>d[i];
for(int j=0;j<=m;j++)
{for(int k=0;k<=800;k++)
{
int t=k-p[i]+d[i];
dp[i][j][k]=dp[i-1][j][k];
if(j-1<0||t<0||t>800) continue;//该状态不可选取
dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][t]+p[i]+d[i]);
}
}
}
int k=0;//找到最小差值
while(dp[n][m][400-k]<0&&dp[n][m][400+k]<0) k++;
if(dp[n][m][k+400]>dp[n][m][400-k]) k=400+k;
else k=400-k;
//查询判断入选的人
int c=0,i=n,j=m,a[N];
while(j)
{
if(dp[i][j][k]==dp[i-1][j][k])
{i--;
continue;
}
a[c++]=i; k-=(p[i]-d[i]);
j--,i--;
}
int pp=0,dd=0;
for(int i=c-1;i>=0;i--)
{
pp+=p[a[i]];
dd+=d[a[i]];
}
printf("Jury #%d\n",T);
printf("Best jury has value %d for prosecution and value %d for defence:\n",pp,dd);
for(int i=c-1;i>=0;i--)//题目要求升序!!!
cout<<" "<<a[i];
cout<<"\n\n";//两个换行符!!!
T++;
}
}
281. 硬币 - 多重背包
给定 N种硬币,其中第 i 种硬币的面值为 Ai,共有 Ci 个。
从中选出若干个硬币,把面值相加,若结果为 S,则称“面值 S能被拼成”。
求 1∼M之间能被拼成的面值有多少个。
思路
i i i 遍历每一个物品,对于每一个物品,用一个数组used存放,该硬币使用了多少次。
对于 j 没组合, j − a [ i ] j-a[i] j−a[i]可组合,且使用次数足够。说明可以组合成功,更新一下。
代码
#include<bits/stdc++.h>
using namespace std;
int c[120],a[120],f[100050],used[110000];//f是bool类型,used存放不同面值下该硬币使用次数
int main()
{
int n,m;
while(cin>>n>>m,n&&m)
{
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>c[i];
memset(f,0,sizeof(f));//初始化
f[0]=1; //面值为0,初始化为1
for(int i=1;i<=n;i++)
{ memset(used,0,sizeof used);//对于不同硬币,进行初始化
for(int j=a[i];j<=m;j++)
{
if(!f[j]&&f[j-a[i]]&&used[j-a[i]]<c[i])//j没组合成,j-a[i]已组合,硬币i使用次数足够
{
f[j]=1;//标记可组合
used[j]=used[j-a[i]]+1;
}
}
}
int ans=0;
for(int i=1;i<=m;i++)//计算有多少可组合
if(f[i]) ans++;
cout<<ans<<'\n';
}
}