51nod 1163 最高的奖励

12 篇文章 0 订阅
4 篇文章 0 订阅

题目链接

贪心,优先队列。

题意:
给了 n n n个任务的结束时间和奖励,让我们安排做任务的顺序,使最终能够得到的奖励最大。

题解:

朴素做法:
首先将所有的任务根据奖励的大小从大到小排序,如果奖励大小相同,结束时间晚的排在前面。贪心的策略就是,将该任务结束时间前第一个没有被安排任务的时间分给该任务。由于结束时间的数据较大,直接贪心会出错,我们发现,总共只有 n n n个任务,也就是最多只需要 n n n个单位的时间就可以做完所有任务。因此,我们在贪心时,将当前任务的结束时间与 n n n取一个 m i n min min

朴素版代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 5e4 + 10;
bool flag[MAXN];
struct P {
	ll e, w;
}p[50010];
bool cmp(P a, P b) {
	if (a.w == b.w) {
		return a.e > b.e;
	}
	return a.w > b.w;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	ll n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> p[i].e >> p[i].w;
	}
	sort(p, p + n, cmp);
	ll ans = 0;
	for (int i = 0; i < n; i++) {
		ll pos = p[i].e;
		pos = min(pos, n); // 最多用n个单位时间
		while (pos && flag[pos]) pos--; // 找到结束时间之前第一个没有任务的时间
		if (pos) {
			flag[pos] = true;
			ans += p[i].w;
		}
	}
	cout << ans << endl;
	return 0;
}

优化一点的版本是贪心+优先队列。

首先将所有的任务按照结束时间从早到晚排序。然后判断每个任务是否插入优先队列当中。如果当前任务的结束时间晚于队列的长度,也就是已经耗费的时间。那么将该任务的奖励直接加入队列,同时答案加上该任务奖励。如果,早于当前队列的长度,那么我们先将该任务加入队列,然后将队列当中奖励最少的任务拿出。

这样最终队列当中的任务奖励总和就是答案。时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

优化版代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 5e4 + 10;
bool flag[MAXN];
priority_queue<int, vector<int>, greater<int> > q;
struct P {
	ll e, w;
}p[50010];
bool cmp(P a, P b) {
	return a.e < b.e;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	ll n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> p[i].e >> p[i].w;
	}
	sort(p, p + n, cmp);
	ll ans = 0;
	for (int i = 0; i < n; i++) {
		if (p[i].e > q.size()) { // 晚于已经花费的时间,直接加入队列
			ans += p[i].w;
			q.push(p[i].w);
		}
		else { // 早于已花费时间,替换队列中奖励最少的任务
			ans += p[i].w;
			q.push(p[i].w);
			ans -= q.top();
			q.pop();
		}
	}
	cout << ans << endl;
	return 0;
}

并查集版本:

先将所有的任务按照奖励降序排序,然后使用并查集依次判断是否有当前任务可用的时间点。
我们使用并查集维护当前未使用的时间点,其中 f [ x ] = a f[x]=a f[x]=a表示结束时间为 x x x的任务可以在时间点 a a a完成。

时间复杂度是 O ( N a ( N ) ) O(Na(N)) O(Na(N)) a ( N ) a(N) a(N)是并查集的查找过程,接近于1。

并查集版代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 5e4 + 10;
int f[MAXN];
struct P {
	ll e, w;
}p[50010];
bool cmp(P a, P b) {
	return a.w > b.w;
}
int find(int x) {
	if (x <= 0) return -1;
	if (f[x] == x) {
		return f[x] = x - 1; // 当前时间点可用
	}
	else {
		return f[x] = find(f[x]);
	}
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	ll n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> p[i].e >> p[i].w;
		f[i] = i;
	}
	sort(p, p + n, cmp);
	ll ans = 0;
	for (int i = 0; i < n; i++) {
		if (find(p[i].e) >= 0) { // 查找是否存在可用的时间点
			ans += p[i].w;
		}
	}
	cout << ans << endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值