刷题(贪心算法)

文章提供了多个编程问题的解决方案,主要涉及贪心算法和排序策略。例如线段覆盖问题通过按结束时间排序解决,删数问题采用字符串存储和从左往右的极大值策略,纪念品分组问题利用双指针法,跳跳问题通过降序排序选择最大和最小石头跳跃,均分纸牌问题通过传递纸牌实现平衡,最后是两个不同场景下的贪心算法应用示例。
摘要由CSDN通过智能技术生成

一,凌乱的yyy / 线段覆盖 - 洛谷

思路:按结束时间从小到大排序,然后比较,当竞赛开始的时间大于上一个竞赛的结束时间就入选了。
因为是结束时间排序,所以第一个符合的开始时间就是最好的。

AC代码

#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int a[N];

struct contest
{
	int begin;
	int end;
}c[N];

bool cmp(contest a, contest b) {
	return a.end < b.end;
}

int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> c[i].begin >> c[i].end;
	sort(c + 1, c + 1 + n, cmp);

	int last = -1,count=0;
	for (int i = 1; i <= n; i++) {
		
		if (c[i].begin >= last) {//等于结束时间也是可以的
			count++;
			last = c[i].end;
		}
		else continue;
	}
	cout << count<<endl;
	return 0;
}

二,删数问题 - 洛谷

思路:(1)这道题数字有240位,且要对其进行删除数字操作,可以改为字符串存储。操作方便且不会溢出。

(2)贪心策略:从左往右找出极大值(包括左右端点),删除极大值,如此反复操作。

(3)数字中对0的特殊处理,如果0出现再数字前面,如0078,012,这样的直接删除0,防止后续贪心策略误判。

AC代码

#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;

//从前往后极大值

int main()
{
	vector<char> s;
	string str;
	int m;
	cin >> str >> m;
	for (int i = 0; i < str.length(); i++) s.push_back(str[i]);

	while (m--) {
		while (s[0] == '0') s.erase(s.begin());//0出现再数字前面,直接删除0
		if (s[0] > s[1]) s.erase(s.begin());//第一个数比第二个数大,则删第一个
		else {//从前往后极大值
			for (int i = 1; i < s.size(); i++) {
				if (i == s.size() - 1) {//特判,防止越界
					if (s[i] >= s[i - 1]) {
						s.erase(s.begin() + i);
						break;
					}
				}
				else {
					if (s[i] >= s[i - 1] && s[i] >= s[i + 1]) {
						s.erase(s.begin() + i);
						break;
					}
				}
			}
		}
	}

	if (s[0] == '0') {
		int flag = 0;
		s.erase(s.begin());
		int n = s.size();
		for (int i = 0; i < n; i++) {
			if (flag == 0 && s[i] == '0') continue;
			else {
				cout << s[i];
				flag = 1;
			}
		}
		if (flag == 0) cout << "0";
	}
	else {
		for (int i = 0; i < s.size(); i++) {
			cout << s[i];
		}
	}

	return 0;
}

三,[NOIP2007 普及组] 纪念品分组 - 洛谷

思路:将物品从小到大排序,然后使用双指针,是最大的和最小的放在一起(如果价格之和没有超出给定的整数)

#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
const long long N = 3e4 + 5;
int a[N];

//20 20 30 50 60 70 80 90 90
int main()
{
	int n, val;
	cin >> val >> n;
	for (int i = 0; i < n; i++) cin >> a[i];
	sort(a, a + n);

	int l = 0, r = n - 1;
	int count = 0;
	while (l <= r) {
		if (a[l] + a[r] > val) {//价格之和大于给定的数,那么最大的数单独包装,右指针向左移
			r--;
			count++;
		}
		else {//价格之和小于给定的数,那么最大的和最小的包装在一起,两指针移动
			count++;
			l++; r--;
		}
	}
	cout << count << endl;
	return 0;
}

四,跳跳! - 洛谷

思路:倒序排序,将0(作为地面)插入数组,双指针,右边动,记录一次,左边动,记录一次。先对石头高度进行降序排序,然后使用双指针来进行贪心选择。每次选择最大和最小的石头进行跳跃,并计算体力值的增加。这样就能得到跳跃过程中耗费的最大体力值。

AC代码

#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;

// 定义比较函数,用于排序时候降序排列
bool cmp(int a, int b) {
	return a > b;
}

// 主函数
int main()
{
	int n, a[305];
	cin >> n; // 输入石头的个数
	for (int i = 0; i < n; i++) cin >> a[i]; // 输入每块石头的高度
	a[n] = 0; // 在数组末尾添加一个高度为0的虚拟石头,便于处理边界情况
	sort(a, a + n + 1, cmp); // 对石头高度进行降序排序,方便后面进行贪心选择

	int l = 0, r = n; // 使用双指针l和r,l指向当前选择的石头中最小的,r指向最大的
	long long sum = 0; // 用于记录跳跃所消耗的总体力值

	// 贪心选择,每次选择最大和最小的石头进行跳跃,并计算体力值
	while (l < r) {
		sum += (a[r] - a[l]) * (a[r] - a[l]); // 计算从最大石头跳到最小石头的体力值
		r--;
		sum += (a[r] - a[l]) * (a[r] - a[l]); // 计算从次大石头跳到次小石头的体力值
		l++;
	}

	cout << sum << endl; // 输出最终的总体力值
	return 0;
}

五,[NOIP2002 提高组] 均分纸牌 - 洛谷

思路:设定从左往右的顺序,求出平均值然后遍历,如果多了就传递给下一堆,少了就让下一堆传过来(也就是传一个负数给下一堆)。

AC代码

#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;
const long long N = 1e2 + 5; 

int a[N]; 

int main()
{
	int n, sum = 0;
	cin >> n; 
	for (int i = 0; i < n; i++) {
		cin >> a[i]; 
		sum += a[i]; // 累加每堆纸牌数量,计算总和
	}

	int val = sum / n; // 计算纸牌的平均数量
	int count = 0; // 定义整数变量count,用于记录移动次数

	// 遍历纸牌堆,将多出的纸牌移动到相邻的堆中,使得每堆纸牌数逐渐接近平均值
	for (int i = 0; i < n - 1; i++) {
		if (a[i] != val) { // 如果当前堆的纸牌数量与平均值不相等
			count++; // 移动次数加1
			a[i + 1] += a[i] - val; // 将多出的纸牌移动到下一堆中
		}
	}
	cout << count; // 输出移动次数
}

六,贪心算法练习 - Virtual Judge

思路:按分值大的排序,将作业安排再当天,如果当天没空,则安排再前一天

AC代码

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e3 + 5;

struct work
{
	int ddl; // 截止日期
	int score; // 分值
	int flag; // 标记作业是否被安排在某天
};

// 自定义排序函数,按照分值大的排在前面,如果分值相同,则按截止日期小的排在前面
bool cmp(work a, work b) { 
	if (a.score == b.score) return a.ddl > b.ddl;
	else return a.score > b.score;
}


int main()
{
	int T;
	cin >> T; 
	while (T--) {
		int n; 
		struct work a[N]; 
		cin >> n;
		int t_max = 0;
		for (int i = 1; i <= n; i++) cin >> a[i].ddl; // 输入每个作业的截止日期
		for (int i = 1; i <= n; i++) cin >> a[i].score; // 输入每个作业的分值
		for (int i = 1; i <= n; i++) a[i].flag = 0; // 初始化作业的标记为0
		sort(a + 1, a + 1 + n, cmp); 

		int t[N] = { 0 }; // 定义一个数组t用来记录每天是否有作业安排,初始化为0
		for (int i = 1; i <= n; i++) { // 遍历每个作业
			if (t[a[i].ddl] == 0) { // 如果当天没有作业安排
				t[a[i].ddl] = 1; // 将当天标记为有作业安排
				a[i].flag = 1; // 将当前作业标记为已经安排在当天
			}
			else { // 当天已经有作业安排
				int count = a[i].ddl - 1; // 从前一天开始往前找可安排的天数
				while (1) {
					if (count <= 0) break; // 如果已经到第一天,就跳出循环
					if (t[count] == 0) { // 如果前一天没有作业安排
						a[i].flag = 1; // 将当前作业标记为已经安排在前一天
						t[count] = 1; // 将前一天标记为有作业安排
						break; // 跳出循环
					}
					count--; // 继续向前找
				}
			}
		}

		int ans = 0; 
		for (int i = 1; i <= n; i++) {
			if (a[i].flag == 0) ans += a[i].score; // 如果作业未被安排,则累加其分值
		}
		cout << ans << endl; // 输出总的未安排作业的分值
	}
	return 0;
}

七,贪心算法练习 - Virtual Judge

思路:按 攻击/血量 的比值进行排序,先把比值大的干掉。

AC代码

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>

struct hero
{
	int hp;
	int dps;
	double rate;
};

// 自定义比较函数,用于结构体数组的快速排序,按照 rate 降序排序
int cmp(const void* a, const void* b)
{
	return (*(struct hero*)b).rate > (*(struct hero*)a).rate ? 1 : -1;
}

// 先打 伤害/血量 比值高的,对方先出手
int main()
{
	int n;
	while (scanf("%d", &n) != EOF && n != 0) {
		struct hero a[30]; 
		for (int i = 0; i < n; i++) {
			scanf("%d%d", &a[i].dps, &a[i].hp); 
			a[i].rate = 1.0 * a[i].dps / a[i].hp; // 计算每个英雄的伤害/血量比值
		}
		qsort(a, n, sizeof(a[0]), cmp); // 对英雄数组按照 rate 降序排序,这里可以用C++里的sort()排序,更方便

		int sum = 0;
		int count = 0;
		while (a[n - 1].hp > 0) // 当最后一个英雄的血量大于 0 时,即还有敌方英雄存活,继续进行战斗
		{
			for (int i = 0; i < n; i++) {
				if (a[i].hp > 0) sum += a[i].dps; // 计算本回合的总伤害,每个存活的英雄都会进行攻击
			}
			a[count].hp -= 1; // 将敌方当前轮到出手的英雄的血量减去 1,表示我方进行了攻击
			if (a[count].hp == 0) count++; // 如果当前轮到出手的敌方英雄血量为 0,表示他已经死亡,更新轮到下一个出手的敌方英雄
		}
		printf("%d\n", sum); // 输出战斗过程中我方的总伤害
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值