今日总结2024.5.14

今日复习了分组背包的用法

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用lld
    return 0;
}

也可以用f[i,j-v]优化掉一维循环

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值