今日复习了分组背包的用法
P1064 [NOIP2006 提高组] 金明的预算方案
题目描述
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 𝑛n 元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 | 附件 |
---|---|
电脑 | 打印机,扫描仪 |
书柜 | 图书 |
书桌 | 台灯,文具 |
工作椅 | 无 |
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 0 个、1 个或 2 个附件。每个附件对应一个主件,附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的 n 元。于是,他把每件物品规定了一个重要度,分为 5 等:用整数 1∼5 表示,第 5 等最重要。他还从因特网上查到了每件物品的价格(都是 1010 元的整数倍)。他希望在不超过 n 元的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第 j 件物品的价格为 vj,重要度为wj,共选中了 k 件物品,编号依次为 1,j2,…,jk,则所求的总和为:
𝑣𝑗1×𝑤𝑗1+𝑣𝑗2×𝑤𝑗2+⋯+𝑣𝑗𝑘×𝑤𝑗𝑘
请你帮助金明设计一个满足要求的购物单。
输入格式
第一行有两个整数,分别表示总钱数 𝑛n 和希望购买的物品个数 𝑚m。
第 2 到第 (m+1) 行,每行三个整数,第 (i+1) 行的整数 vi,pi,qi 分别表示第 i 件物品的价格、重要度以及它对应的的主件。如果 qi=0,表示该物品本身是主件。
输出格式
输出一行一个整数表示答案。
本题是将每个主件看成一组,每组内的物品分别为主件+附件的搭配,因为每个附件在选主件的情况下是选或者不选2^n因此可以使用二进制枚举,然后再用分组背包,对组内每个物品进行判断是否能选取最大值即可
//参考yxc大佬代码
#include <iostream>
#include <algorithm>
#include <utility>
#include <vector>
using namespace std;
const int N=4e4,M=70;
typedef pair<int,int> PII;
#define x first
#define y second
PII mas[M];//存主要物品
vector<PII> sv[M];//存附属
int f[N];//选到第i组,总价格不超过j的最大价格乘以重要度值int main(){int n,m;cin>>m>>n;for(int i=1;i<=n;i++){int v,p,q;cin>>v>>p>>q;if(q) sv[q].push_back({v,p*v});else mas[i]={v,v*p};//把价格和价格乘以重要度存入}for(int i=1;i<=n;i++)for(int j=m;j;j--){for(int k=0;k<1<<sv[i].size();k++){//枚举组内所有物品选还是不选int v=mas[i].x,w=mas[i].y;for(int u=0;k<sv[i].size();u++)if(k>>u&1==1){v+=sv[i][u].x,w+=sv[i][u].y;} //二进制枚举算出每组选法的体积和价值if(j>=v) f[j]=max(f[j],f[j-v]+w);}}cout<<f[m];return 0;
}
P1060 [NOIP2006 普及组] 开心的金明
弱化版,01背包即可
#include <iostream>
#include <algorithm>
#include <utility>
using namespace std;
const int N=30,M=3e4+5;
typedef pair<int,int> PII;
int v[N],f[M],w[N];//f[i,j]表示选前i个物品,体积不超过j的最大总和int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);int n,m;cin>>m>>n;for(int i=1;i<=n;i++){int a,b;cin>>a>>b;//价格,重要度v[i]=a;w[i]=a*b;}for(int i=1;i<=n;i++)for(int j=m;j>=v[i];j--) f[j]=max(f[j],f[j-v[i]]+w[i]);cout<<f[m];return 0;
}
AcWing 1021. 货币系统
题目描述
给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。
输入格式
第一行,包含两个整数n和m
接下来n
行,每行包含一个整数,表示一种货币的面值。
输出格式
共一行,包含一个整数,表示方案数。
数据范围
n≤15,m≤3000
输入样例:
3 10
1
2
5
输出样例:
10
实际上就是一个完全背包的方案问题,求n种面值的货币组成面值为m的方案数
状态表示为从前i个物品中选,面值为j的方案
属性为数量count
状态表示为f[i-1,j]第i个面值不选,由前i-1个面值组成面值为j的方案转移而来
f[i-1,j-v],f[i-1,j-2v]......f[i-1,j-sv]选第i个面值,且选k个,k取决于枚举到的体积j
k*v<=j
#include <iostream>
using namespace std;
const int N = 3010;
int v[N];
long long f[N][N];//会爆int
int main(){int n,m;scanf("%d%d",&n,&m);f[0][0] = 1;for(int i = 1;i <= n;i ++) scanf("%d",&v[i]);for(int i = 1;i <= n;i ++){for(int j = 0;j <= m;j ++){for(int k = 0;v[i] * k <= j;k ++){f[i][j] += f[i - 1][j - v[i] * k];}}}printf("%lld",f[n][m]);//输出longlong用lldreturn 0;
}
也可以用f[i,j-v]优化掉一维循环