2024杭电多校第九场-1006-融合矿石

题目

融合矿石

题干

输入

输出

对于每组测试数据:

输出一行一个整数,表示最大总价值。

输入样例

1

1 1 1 1 1 3 3 3 4 5

2 10

4 2

6 5

输出样例

30

样例分析

我们先大致理解题目的意思。总体而言是为了得到更高的价值,而价值由矿石中金辉石的含量和矿石的质量决定。接着再分析一下样例,来进一步理解题目的意思。

样例中,我们的背包容量是10,物品种类是2。若我们两种矿石各取一个,此时背包塞满了。若不融合,则矿石1的金辉石含量是 2 / 4  = 50 %,而矿石2的含量是 83 % 。根据 价值体系 ,可以得到矿石 1 的单位价值是 1 ,矿石 2 的单位价值是 3 。不融合,总价值就是 4 + 6 * 3 = 22。融合,则金辉石含量变为(2 + 5)/(4 + 6)= 70 % ,同样根据价值体系得到单位价值是3,不难得到总价值为30 。

思路分析

因为代码不是完全由自己写的,希望能更好的梳理一下思路。

首先很容易就能联想到完全背包问题。完全背包问题的特征是每种物品可以取无限个。此处的每种矿石也可以取无限个。

对于一般的完全背包问题,我们考虑的一般是:取到第 i 件物品时,物品总重量不超过 w 时,所能获得的最大价值。那本题的 f  [ i ] [ j ] 是否就是,考虑取i件物品时,包中的总价值呢?其实不是。

本题有一个非常重要的信息点:保证随着"金辉石"占比的增大,矿石的单位价值是单调不减的。也就是说,在背包容积一定的情况下,随着所获得的矿石中金辉石的占比增大,背包中矿石总价值一定也是单调不减的。因此我们背包问题中的 “ 价值 ” 在这道题中的体现就是,金辉石的总质量

此处应用完全背包的板子,优化了第一维,这部分代码如下:

为了便于理解所以数组写了全称

for (int i = 1; i <= n; i ++ )
    for (int j = 0; j <= m-weight[i]; j ++ )
        f[j+weight[i]] = max(f[j+weight[i]], f[j] + b[i]);

即 f [ m ] 的含义是,背包容量是 m 时所能获得的最大的金辉石的质量

接着我们考虑一下,我们已经拿到了目前能拿到的最大质量的金辉石,那对于已经获得的这些矿石,我们是否要融合呢?还是以样例为例,决定我们是否融合的因素在于,融合后的总价值是否比原先高了,高就融合,不高就保留。

因此我们需要计算每一块选中的矿石的金辉石含量占比并根据价值体系得出它的单位价值

代码如下:

需要注意的是,题目的边界(如50%应该算在更低的一边,举个栗子也就是说我的及格线虽然划到60,但你60分依然算不及格,你需要比60分高一点才能让你及格~),做个判断即可。以及内部需要 / 10,因为是分数的形式。

#define rep(i, a, n) for(int i = a; i <= n; i++)

int vv[N] = {0};
	rep(i, 1, m) {
		vv[i] = value[f[i] * 10 / i + (f[i] * 10 % i != 0)];
	}

那是否选择融合的过程怎么考虑呢?其实也是一个背包问题

很遗憾我当时并没有想到这个思路,所以不知道佬们的灵光乍现是否有迹可循……遂来讲解代码

dp[j] 表示不包括当前重量 i 的矿石时,已经达到的最大价值。

i * vv[i] 表示如果增加重量 i 的矿石,由于单位重量价值为 vv[i],所以增加的价值是 i * vv[i]

通过比较现有的 dp[i+j] 和新计算的 dp[j] + i * vv[i],选择更大的值进行更新,这样确保了在总重量为 i+j 时,能够获得的最大价值

同样附上代码:

	int dp[N] = {0};
	int maxx = 0;
	rep(i, 1, m) {
		for (int j = 0; i + j <= m; j++) {
			dp[i + j] = max(dp[i + j], dp[j] + i * vv[i]);
			maxx = max(maxx, dp[i + j]);
		}
	}

最后用maxx记录不同融合方式下的最大价值即可。

附上AC代码:

码风不适请见谅

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define int long long
#define rep(i, a, n) for(int i = a; i <= n; i++)
#define per(i, a, n) for(int i = n; i >= a; i--)
typedef pair<int, int> PII;
const int mod = 1e9 + 7;
const int N = 9010;
int value[11];
int n, m;
int weight[N], w[N];
int b[N];

void solve() {
	int f[N] = {0};
	rep(i, 1, 10) cin >> value[i]; 
	cin >> n >> m;
	for (int i = 1; i <= n; i ++ ) {
		cin >> weight[i];
		cin >> b[i];
	}
	for (int i = 1; i <= n; i++)
		for (int j = weight[i]; j <= m; j++ )
			f[j] = max(f[j], f[j - weight[i]] + b[i]);
	int vv[N] = {0};
	rep(i, 1, m) {
		vv[i] = value[f[i] * 10 / i + (f[i] * 10 % i != 0)];
	}
	int dp[N] = {0};
	int maxx = 0;
	rep(i, 1, m) {
		for (int j = 0; i + j <= m; j++) {
			dp[i + j] = max(dp[i + j], dp[j] + i * vv[i]);
			maxx = max(maxx, dp[i + j]);
		}
	}
	cout << maxx << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t;
	cin >> t;
	while (t--)
		solve();
}

很巧妙的背包考虑方式,可能因为本人做题偏少,且完整代码并非由我提交,思路也是队友提供的。感觉这道题值得赛后整理一下,遂写了这篇题解,也是完善了赛时的思考。

如有问题,感谢批评指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值