ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

贪心算法的常见问题Ⅰ(详解!)

2021-06-08 20:01:24  阅读:288  来源: 互联网

标签:常见问题 int ++ 详解 maxn ans n1 include 贪心


贪心算法详解(第一部分)

一、最基本问题

常用技巧:排序、维护最优解

例题1:洛谷P1094 纪念品分组

题目大意:

给你n个纪念品,让你发给大家,要求每个人的纪念品数目不能超过2个,并且要使得大家获得的纪念品价值相对比较均衡,每个人的纪念品总价值不能超过m,请问最少能分出多少组。

算法思想:

先给纪念品的价值排个序,排序后前后同时组成纪念品组,这样能保证价值相对均衡。
AC代码如下:

#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 3e4 + 5;
int a[maxn], n, w, ans;

int main(){
	cin >> w >> n;
	for(int i = 0;i < n;i++)
		cin >> a[i];
	sort(a, a + n);
	int i = 0,j = n - 1;
	while(i <= j){
		if(a[j] + a[i] <= w){
			i++, j--;
			ans++;
		}else	j--, ans++;
	}
	cout << ans;
	return 0;
} 

例题2:洛谷P1208 混合牛奶

题目大意:

每位奶农每天只能提供一定量的牛奶,并且他们的单价不同,现在给你一个需求,问你最少花费的多少钱能买到你需要的牛奶量。

算法思想:

他给你的是单价,你就按照单价排序,然后从最便宜开始,依次购买。一旦发现买多了,就退还到上一步操作,对目前可以购买的最小单价去买你还需要的那部分即可。
思考:如果是给你总价呢?你就计算性价比,按照性价比排序即可。
思考:如果对购买的公司家数有限制呢?那这就是一种动态规划的问题了。后面再去补充。
AC代码如下:

#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 2e6 + 5;
struct Node{
	int p, b;
}a[maxn];
int n, m, ans, cnt;

bool cmp(Node n1, Node n2){
	return n1.p < n2.p;
}

int main(){
	cin >> m >> n;
	for(int i = 0;i < n;i++)
		cin >> a[i].p >> a[i].b;
	sort(a, a + n, cmp);
	for(int i = 0;i < n;i++){
		cnt += a[i].b;
		if(cnt >= m){
			cnt -= a[i].b;
			ans += (m - cnt)*a[i].p;
			break;
		}else
			ans += a[i].b*a[i].p;
	}
	cout << ans;
	return 0;
}

二、区间调度问题

例题3:洛谷P1803 活动安排问题

题目大意:

给你一个时间表,上面有n个日程计划,每个日程计划对应着它的起止时间。你没有分身术,不能同时去参加两个活动,问你最多能参加多少个活动?

算法分析:

这题显然是一种贪心,但是如何去确定你的贪心策略呢?一个日程,我们可以得到三个关于它的属性:开始时间、结束时间、经历时长。
策略一:如果按照开始时间去排序,也许一个活动,它开始的时间很早,但是它结束的时间很晚,那肯定也不能选取,第一种方案pass!
策略二:
如果按照经历的时长去排序,时间短的活动也许覆盖住了很多时间长的活动,这也是不合理的。
于是我们确定了最终的贪心方案,按照结束时间去排序。
排序完成之后呢?我们可以确定的是,最早结束的活动我们一定是要参加的,因为参加它之后我们再去参加其他活动不会影响最优解。那么如何去确定下一个活动我们是否能够参加呢?只需要比较上一个活动结束的时间和下一个活动开始的时间,如果开始的时间在结束的时间的后面,那么这个活动就可以去参加。然后下一个活动就变成了当前活动,一次类推即可!
AC代码如下:

#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 1e6 + 5;
struct Time{
	int a, b;
}t[maxn];
int n, ans, pos;

bool cmp(Time n1, Time n2){
	return n1.b <= n2.b;
}

int main(){
	cin >> n;
	for(int i = 0;i < n;i++)
		cin >> t[i].a >> t[i].b;
	sort(t, t + n, cmp);
	ans = 1, pos = t[0].b;
	for(int i = 1;i < n;i++){
		if(t[i].a >= pos){
			pos = t[i].b;
			ans++;
		}
	}
	cout << ans;
	return 0;
}

练习题1:HDU 2037 今年暑假不AC

这个题的做法和上面那个例题完全一样,我就不写思路了,AC代码附上:

#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 105;
int n, pos, ans;
struct Node{
	int beg, en;
}a[maxn];
bool cmp(Node n1, Node n2){
	return n1.en <= n2.en;
}

int main(){
	while(cin >> n && n){
		ans = 1;
		for(int i = 0;i < n;i++)
			cin >> a[i].beg >> a[i].en;
		sort(a, a + n, cmp);
		pos = a[0].en;
		for(int i = 1;i < n;i++){
			if(a[i].beg >= pos){
				ans++;
				pos = a[i].en;
			}
		}
		cout << ans << endl;
	}
	return 0;
} 

练习题2:HDU 1789 Doing Homework again

算法分析:

他有n个作业要写,每个作业具有一下属性:截止时间、扣的分数
最理想的情况就是所有作业都能做完,这样扣的分数最少。但是现实情况可能难以如此理想,所以我们要制定一个优先顺序,哪样的作业要先去做。因为每个作业都有截止日期,所以最着急的肯定就是马上就要去交的作业,所以截止时间最优先;那如果截止时间相同呢?那必然就去做扣分最严重的那一门科目。这个道理是显然的,我们就按此顺序去排序。
排好序之后,我们指定一个时间以天数为单位:k = 1,意思就是从第一天开始去安排做作业的顺序(因为每个作业都要做一天!)
如果到了某一天,有作业交不上了,我们就怀疑是不是之前安排的不妥当,所以去检查前面的安排,查到一个扣分最少的作业,去取代不能完成扣分又较多的那个作业,即可!
AC代码如下:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int maxn = 1005;
int t, n;
struct Class{
	int day, score;
	int book;//完成状态 
}c[maxn];

bool cmp(Class n1, Class n2){
	if(n1.day != n2.day)
		return n1.day < n2.day;
	return n1.score > n2.score;
}

int main(){
	cin >> t;
	while(t--){
		cin >> n;
		for(int i = 1;i <= n;i++)
			cin >> c[i].day;
		for(int i = 1;i <= n;i++){
			cin >> c[i].score;
			c[i].book = 1;// 先假设都可以完成
		}
		sort(c + 1, c + n + 1, cmp);
		int ans = 0, k = 1;
		for(int i = 1;i <= n;i++){
			if(c[i].day >= k){
				k++;//该作业可以当天完成,安排下一个的作业 
				continue; 
			}
			int pre = c[i].score, pos = i;// 第i个作业无法当天完成 
			for(int j = 1;j <= i;j++){
				if(pre > c[j].score && c[j].book){
					pre = c[j].score;
					pos = j;
				}
			}
			ans += pre;
			c[pos].book = 0;
		}
		cout << ans << endl;
		memset(c,0,sizeof(c));
	}
	return 0;
}

拓展练习1:HDU 1050 Moving Table

算法分析:

一个桌子从一个房间移动到另一个房间可以在十分钟内完成,从房间 I 移动到房间 J 的时候,除了[I,J]的部分,其他部分桌子的移动是可以同时进行的。这个题其实很简单啊,就是尽可能地让更多的桌子在相应的移动区间同时移动。然后计算最少用时,所谓最少用时,其实就是最少移动次数乘以10即可。那究竟如何才能知道最少移动次数呢?这里有个坑,大家先看看这幅图:
请仔细观察房间分布在这里插入图片描述
如果我有两个桌子,分别从:1 -> 3,4 -> 5看似这两个移动是不重叠的,实际上重叠了,因为3和4号房间是相对的,他们共用了这段走廊。
而且,移动的方向也不一定是从小房间到大房间,也可能是从大房间到小房间.所以我先需要两步处理:第一、让上下房间位置相对的房间号一致;第二、因为无论是大号房间到小号房间或者是反过来,走过的走廊都是一样的,所以如果是逆序则交换。
存储好了之后,重点来了:我们首先按照路径的起点排序,然后和之前的区间调度方法一样,只是说需要反复使用区间调度,每次都尽可能多的去解决问题,直到所有需要搬桌子的房间都完成了,那么也就完成这次的反复区间调度。
AC代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 205;
int n, t, x, y;
struct Node{
	int beg, en;
	int book;
}a[maxn];

bool cmp(Node n1, Node n2){
	return n1.beg < n2.beg;//一定要按照起点排序 
}

int main(){
	cin >> t;
	while(t--){
		cin >> n;
		for(int i = 0;i < n;i++){
			cin >> x >> y;
			if(x % 2)	x++;
			if(y % 2)	y++;// 使得上下房间号位置参考一致 
			a[i].beg = min(x, y);
			a[i].en = max(x, y);
		}
		sort(a, a + n, cmp);
		int flag = 1, cnt = 0, next;
		while(flag){
			next = -1;
			flag = 0;
			for(int i = 0;i < n;i++){
				if(a[i].beg > next && !a[i].book){
					next = a[i].en;
					a[i].book = 1;
					flag = 1;
				}
			}
			cnt++;
		}
		--cnt;
		cout << cnt*10 << endl;
		memset(a,0,sizeof(a));
	}
	return 0;
}
关于上面这个题,其实还有一种思路,大家可以去试试,大家可以看到,如果走廊上每一个点被走过的次数最大值是x,则区间调度了x次,那么时间就是10*x,这个方法有点类似于前缀和差分。大家可以去试一试,应该是可以做的,我们这里只讨论贪心。

二、区间覆盖问题

算法讲解:

给定了一个长度为n的区间,再给出m条线段的左端点和右端点,问最少使用多少条线段可以将整个区间覆盖?贪心的思路就是尽量找出更长的线段。一般是这样的解题步骤:
一、把每个线段按照左端点升序排序
二、假设已经覆盖的区间是[left,right],在剩下的线段种找出所有左端点小于等于right且右端点最大的线段,把这个线段加入到已经覆盖了的区间上,更新完成覆盖的区间。然后不断重复这个操作。

关于区间覆盖的问题、多机调度的问题下一篇再更新(立flag)

标签:常见问题,int,++,详解,maxn,ans,n1,include,贪心
来源: https://blog.51cto.com/u_15262702/2883339

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有