第十三届蓝桥杯b组国赛dp问题

第十三届蓝桥杯 b 组国赛 d p 问题 \Huge{第十三届蓝桥杯b组国赛dp问题} 第十三届蓝桥杯b组国赛dp问题

刷题的时候发现往年国赛题中有三道dp问题,而且还都是背包问题,正好最近没写过dp,那就简单整理一下,尽量把我思路整理清楚hhh。

关于背包问题,可以查看这篇博客:背包九讲——九种背包问题的算法思路+代码分析-CSDN博客

题目链接:备赛蓝桥杯 - 蓝桥云课 (lanqiao.cn)

2022

题意

2022由10个不同的正整数相加得到的方案个数。

思路

我们可以将本问题看作是01背包,那么此时问题可以改为:有编号为 1 , 2 , 3 , . . . , 2022 1,2,3,...,2022 1,2,3,...,2022的2022个物品,每个物品的体积和价值都等于它的编号,因此物品的体积和价值可以看作一个限制条件,然后需要选10个数,作为第二个限制条件。

那么本题的限制条件就有两个,然后呢?

那不就是一道二维费用流背包吗?

众所周知,二维费用流背包就是在01背包的基础上加了一个或多个限制条件,那我们只需将加一层枚举,来枚举体积和个数即可。

标程

void Solved() {//在二维01背包的基础上增加一维,用来枚举所选个数
	long long f[2023][11][2023];
	for(int i = 0; i <= 2022; i ++ ) f[i][0][0] = 1;
		
	for(int i = 1; i <= 2022; i ++ )
		for(int j = 1; j <= 10; j ++ )
			for(int k = 1; k <= 2022; k ++ ) {
				f[i][j][k] = f[i - 1][j][k];
				if(k >= i) {
					f[i][j][k] += f[i - 1][j - 1][k - i];
				}
			}
	cout << f[2022][10][2022] << endl;
}

void Solved() {//优化一维,详细优化思路可以查看上面背包问题的博客
	long long f[11][2023];
	f[0][0] = 1;

	for(int i = 1; i <= 2022; i ++ )
		for(int j = 10; j >= 1; j -- )
			for(int k = i; k <= 2022; k ++ ) {
				f[j][k] += f[j - 1][k - i];		
			}
	cout << f[10][2022] << endl;
}

搬砖

题意

给出n个砖,每块砖对应的有体积和价值。现要求将砖块摞起来,要求每块砖上面的所有砖加起来的重量不超过其自身的价值。求摞起来的最大总价值。

思路

一道背包问题,但是增加了一些限制条件。

我们考虑:每块砖放与不放的状态是由其上面的砖块重量之和决定,那么我们需要从顶部考虑放砖块,才能保证不影响接下来的结果。

跟据贪心的思路:放在顶部的砖块需要是重量较小,且价值较大的砖块。

那么在枚举的过程中,我们需要按“重量较小,且价值较大”的顺序来优先考虑砖块,因此需要排序。

若有编号为1,2的两块石头,那么其重量和价值关系共有两种情况:

  1. w 1 < w 2 ,   v 1 < v 2 w_1<w_2, ~ v_1<v_2 w1<w2, v1<v2
  2. w 1 < w 2 ,   v 1 > v 2 w_1<w_2, ~ v_1>v_2 w1<w2, v1>v2

对于第一种情况,毫无疑问1应该放在2上面,对于第二种情况,只能1放在2上面。

因此排序规则为: w i + v i < w j + v j w_i+v_i < w_j+v_j wi+vi<wj+vj

标程

int n, f[200001], sum, res;
PII a[1005];

void Solved() {
	cin >> n;
	for(int i = 1; i <= n; i ++ ) {
		cin >> a[i].fi >> a[i].se;
		sum += a[i].fi;
	}
    
	sort(a + 1, a + n + 1, [](PII n1, PII n2) {
		return n1.fi + n1.se < n2.fi + n2.se;
	});

	for(int i = 1; i <= n; i ++ ) {
		for(int j = sum; j >= a[i].fi; j -- ) {
			if(j - a[i].fi <= a[i].se) {
				f[j] = max(f[j], f[j - a[i].fi] + a[i].se);
			}
			res = max(res, f[j]);
		}
	}
	
	cout << res << endl;
}

费用报销

题意

给出若干张发票,每个发票有日期(月日)、价值。要求选出若干张发票,且相互日期间隔不小于k,总和不超过m,求能选出的最大总和。

思路

本题有两个限制条件:

  1. 相互日期间隔不小于k
  2. 总和不超过m

那么这道题就比较简单了,我们首先记录每一天最大的发票价值(当天没有发票就为0,有发票就为最大的数目)。

注意:可能会有某天有多张发票,而题目中k大于等于1,因此不可能选择同一天的发票。

然后枚举每一天,并判断是否需要加上当前天的发票。

标程

#define int long long
int today(int mm, int dd) {
	int mon[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31 ,30, 31, 30, 31};
	int sum = dd;
	for(int i = 1; i < mm; i ++ ) sum += mon[i];
	return sum;
}

void Solved() {
    int n, m, k, da[370] = {0};
	cin >> n >> m >> k;
	for(int i = 1; i <= n; i ++ ) {
		int mm, dd, vv; cin >> mm >> dd >> vv;
		int data = today(mm, dd);
		da[data] = max(da[data], vv);
	}
	
	for(int i = 1; i <= 365; i ++ ) {
		int t = max(0ll, i - k);
		if(da[i] + da[t] < m) {
			da[i] = max(da[i - 1], da[i] + da[t]);
		} else {
			da[i] = da[i - 1];
		}
	}
	
	cout << da[365] << endl;
}
  • 27
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值