【晴问算法】入门篇—贪心算法—经典例题和个人题解

OJ平台链接https://sunnywhy.com/sfbj/4/4/149

什么是贪心算法?

贪心算法的定义采用局部最优的策略能得到全局最优的结果,即贪心选择性质。
除了贪心选择性质以外,能用贪心算法解决的问题还必须具有最优子结构性质。
最优子结构性质:问题的最优解所包含的子问题的解也是最优的
然而,并不是所有问题都适用贪心算法,完全适用贪心算法的问题必须具有最优子结构性质贪心选择性质。针对不完全适用的问题,利用贪心算法,往往只能得到较优解,而并非是最优解。
通俗地讲,假如贪心算法适用于统一天下的问题,那么,刘皇叔完全不用考虑天下大势,每次只要让当前战役所取得的利益最大,损失最小,他就能统一天下。
贪心选择性质意味着只要局部利益最大化就一定能达到全局利益最大化,追求当前局部利益最大化不会损失往后的局部利益,不存在着为了长远利益,而主动牺牲当前的利益这样的现象,只需考虑眼前的利益最大化就可以了,就一定能够达成全局利益最大化的结果。


1. 最优装箱

题目描述
n n n个箱子需要装上一艘轮船,已知第 i i i个箱子的重量为 w i w_i wi,轮船的载重为 W W W。问在不超过轮船载重的前提下,最多能把多少个箱子装上轮船。

输入描述
第一行两个正整数 n n n W W W 1 ≤ n ≤ 1 0 5 1{\le}n{\le}10^5 1n105 1 ≤ W ≤ 1 0 7 1{\le}W{\le}10^7 1W107),分别表示箱子个数和轮船载重。

第二行 n n n个正整数 w i w_i wi 1 ≤ w i ≤ 1 0 7 1{\le}w_i{\le}10^7 1wi107),表示 n n n个箱子的重量。

输出描述
输出两个整数,分别表示能装上轮船的箱子个数和总重量,用空格隔开。

样例1
输入
5 11
7 2 9 1 11
输出
3 10
解释
能将重量分别为7、2、1的箱子装上轮船(此时箱子个数最多),总重量为10。

下面是对这道题的解答:

贪心策略: 对箱子按照重量从小到大排序,每次拿箱子都拿重量最小的那个箱子,直到不能拿为止(不允许总重量超过轮船载重)。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;

int a[100005]; //表示每个箱子的重量
int main(){
	int i,n,W;
	int sum = 0; //sum表示当前已拿箱子的总重量
	int cnt = 0; //cnt表示当前已拿箱子的数量
	scanf("%d%d",&n,&W); //n表示箱子个数,W表示轮船最大载重
	for(i=0;i<n;i++){
		scanf("%d",&a[i]); //读入每个箱子的重量
	}
	sort(a,a+n); //按箱子的重量从小到大排序,贪心策略是优先选择重量小的箱子
	for(i=0;i<n;i++){
		if(sum+a[i]>W) break; //如果拿了当前的箱子会导致超重的话,就不再继续拿箱子
		else{//如果不会超重,那么拿当前的箱子
			cnt++; //已拿箱子个数加1
			sum+=a[i];//已拿箱子的总重量加上新拿箱子的重量
		}
	}
	printf("%d %d\n",cnt,sum);
	return 0;
}

2. 整数配对

题目描述
有两个正整数集合 S S S T T T,其中 S S S中有 n n n个正整数, T T T中有 m m m个正整数。定义一次配对操作为:从两个集合中各取出一个数 a a a b b b,满足 a ∈ S a \in S aS b ∈ T b \in T bT a ≤ b a \le b ab,配对的数不能再放回集合。问最多可以进行多少次这样的配对操作。

输入描述
第一行两个正整数 n n n m m m 1 ≤ n ≤ 1 0 4 1{\le}n{\le}10^4 1n104 1 ≤ m ≤ 1 0 4 1{\le}m{\le}10^4 1m104),分别表示 S S S T T T中正整数的个数。

第二行 n n n个正整数( 1 ≤ a i ≤ 1 0 5 1{\le}a_i{\le}10^5 1ai105),表示 S S S中的 n n n个正整数。

第三行 m m m个正整数( 1 ≤ b i ≤ 1 0 5 1{\le}b_i{\le}10^5 1bi105),表示 T T T中的 m m m个正整数。

输出描述
输出一个整数,表示最多的配对操作次数。

样例1
输入
3 3
2 5 3
3 3 4
输出
2
解释
2与其中一个3配对,3与另一个3配对,5无法和4配对。因此最多配对两次。

下面是对这道题的解答:

贪心策略: 对集合S和集合T均从小到大排序,针对集合S中的每一个元素,选择集合T中尽可能小的但是又比集合S中的当前元素大的元素,保证配对总数最大,避免非常小的元素配对非常大的元素,导致其它元素配不到对。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;

int main(){
	int n,m,tp,i,j;
	int sum = 0;//表示当前已配对数
	vector<int> v1,v2; //向量v1存储集合S的元素,向量v2存储集合T的元素
	scanf("%d%d",&n,&m);
	for(i=0;i<n;i++){
		scanf("%d",&tp); //读入集合S的元素,允许重复
		v1.push_back(tp); //将元素放入集合S
	}
	sort(v1.begin(),v1.end()); //对集合S的元素从小到大排序
	for(i=0;i<m;i++){
		scanf("%d",&tp); //读入集合T的元素,允许重复
		v2.push_back(tp); //将元素放入集合T
	}
	sort(v2.begin(),v2.end()); //对集合T的元素从小到大排序
	j = 0;
	for(i=0;i<n;){
		if(v1[i]<=v2[j]){ //如果集合S当前元素小于等于集合T当前元素,可以配对
			sum++; //配对数加1
			i++; //配对成功,开始对集合S的下一个元素寻找配对元素
		}
		j++; //考虑集合T的下一个元素
		if(j==m) break; //如果已经到达集合T的最后一个元素,意味着集合T不再有可以配对的元素
	}
	printf("%d\n",sum);
	return 0;
}

3. 最大组合整数

题目描述
现有0~9中各个数的个数,将它们组合成一个整数,求能组合出的最大整数。

输入描述
在一行中依次给出0-9中各个数的个数(所有个数均在0-100之间)。数据保证至少有一个数的个数大于0。

输出描述
输出一个整数,表示能组合出的最大整数。

样例1
输入
1 0 2 0 0 0 0 0 0 1
输出
9220
解释
存在1个0、2个2、1个9,因此能组合出的最大整数是9220

下面是对这道题的解答:

贪心策略: 要想使得输出整数最大,只要让每次输出的数都是当前可用的最大数,例如,先输出9,再输出8,再7,以此类推,最后输出0,始终让当前可用的最大的数字位于整数的高位,这能保证整数最大。如果某个数字有多个可用,那就输出多个,如果某个数字只有0个可用,那就不用,将其跳过。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;

int main(){
	int a[10]; //存储0-9当中每个数的出现次数
	int i;
	int flag = false; //是否是第一个输出的数字
	for(i=0;i<10;i++){
		scanf("%d",&a[i]);
	}
	for(i=9;i>=0;i--){  //从大到小输出数字,最先输出9,最后输出0,这能保证组合而成的整数最大
		if(a[i]>0){
			if(!flag&&i==0){ //如果第一个输出的数字是0,意味着只有0可以用
				printf("0"); //只输出一个0
				break;
			}
			while(a[i]!=0){ //将当前数字的次数全部用完
				printf("%d",i); //输出当前数字
				a[i]--; //可用个数减一
			}
			flag = true; //表示已经输出了第一个数字
		}
	}
	printf("\n");
	return 0;
}

4. 区间不相交问题

题目描述
给定个开区间,从中选择尽可能多的开区间,使得这些开区间两两没有交集。

输入描述
第一行为一个正整数 n n n 1 ≤ n ≤ 1 0 4 1{\le}n{\le}10^4 1n104),表示开区间的个数。

接下来 n n n行,每行两个正整数 x i x_i xi y i y_i yi 0 ≤ x i ≤ 1 0 5 0{\le}x_i{\le}10^5 0xi105 0 ≤ y i ≤ 1 0 5 0{\le}y_i{\le}10^5 0yi105),分别表示第 i i i个开区间的左端点和右端点。

输出描述
输出一个整数,表示最多选择的开区间个数。

样例1
输入
4
1 3
2 4
3 5
6 7
输出
3
解释
最多选择(1,3)、(3,5)、(6,7)三个区间,它们互相没有交集。

下面是对这道题的解答:

贪心策略: 如果要使选取的区间没有交集,必须保证一个区间的左端点比另一个区间的右端点大,而且两两之间如此。所以,优先选取右端点小的而且左端点比上一个选取的区间的右端点大的那个区间,既保证没有交集,又保证右端点较小,为选取更多区间提供了机会。
实际上,在不交叉的前提下,优先选取的是区间长度短并且最靠左的区间,不容易挤占其它区间的空间,如此可选的区间会最多。
例图
如上图所示,红色为选取的区间,蓝色为不选的区间。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;

struct qujian{
	int x,y; //x为区间左端点,y为区间右端点
};

bool cmp(qujian a,qujian b){ //比较函数,y值小的排在y值大的前面
	return a.y<b.y;
}
qujian q[10005];
int main(){
	int i,n;
	int sum = 1; //当前已选择的开区间个数
	int last; //上一个选取的区间的右端点值
	scanf("%d",&n);//n表示区间个数
	for(i=0;i<n;i++){
		scanf("%d%d",&q[i].x,&q[i].y); //读入每个区间的区间左端点和区间右端点
	}	
	sort(q,q+n,cmp); //对每个区间按照区间右端点从小到大排序,优先选右端点小的区间
	last = q[0].y; //区间右端点最小的区间一定可以选取
	for(i=1;i<n;i++){
		if(q[i].x>=last){ //如果当前区间的左端点比上一个选取的右端点还要大,则可以选取,一定没有交集
			sum++;//选取开区间数加1
			last = q[i].y; //存储该区间的右端点,以供下一个选取的区间的左端点和其比较
		}
	}
	printf("%d\n",sum);
	return 0;
}

5. 区间选点问题

题目描述
给定 n n n个闭区间,问最少需要确定多少个点,才能使每个闭区间中都至少存在一个点。

输入描述
第一行为一个正整数 n n n 1 ≤ n ≤ 1 0 4 1{\le}n{\le}10^4 1n104),表示闭区间的个数。

接下来 n n n行,每行两个正整数 x i x_i xi y i y_i yi 0 ≤ x i ≤ 1 0 5 0{\le}x_i{\le}10^5 0xi105 0 ≤ y i ≤ 1 0 5 0{\le}y_i{\le}10^5 0yi105),分别表示第 i i i个闭区间的左端点和右端点。

输出描述
输出一个整数,表示最少需要确定的点的个数。

样例1
输入
3
1 4
2 6
5 7
输出
2
解释
至少需要两个点(例如3和5)才能保证每个闭区间内都有至少一个点。

下面是对这道题的解答:

贪心策略: 对每个区间按照右端点从小到大排序,尽量选取区间的右端点作为确定点,该点能和最多的区间重合。
例图
如上图所示,圆圈指的是确定点。
代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;

struct qujian{
	int x,y;
};

bool cmp(qujian a,qujian b){ //比较函数
	if(a.y!=b.y) return a.y<b.y;
	else return a.x<b.x;
}
qujian q[10005];
int main(){
	int n,i;
	int sum = 1; //sum表示确定的点数
	scanf("%d",&n);
	for(i=0;i<n;i++){
		scanf("%d%d",&q[i].x,&q[i].y);//读入每个区间的左端点和右端点
	}
	sort(q,q+n,cmp); //对区间按照右端点从小到大排序
	int last = q[0].y; //将第一个区间的右端点作为确定点
	for(i=1;i<n;i++){
		if(q[i].x>last){ //如果当前区间的左端点比上一个区间的右端点要大,确定一个新的点,该点为当前区间的右端点
			sum++; //确定点数加1
			last = q[i].y; //修改last为新确定点的值,用于同下一个区间不叫
		}
	}
	printf("%d\n",sum);
	return 0;
}

6. 拼接最小数

题目描述
给定个可能含有前导0的数字串,将它们按任意顺序拼接,使生成的整数最小。

输入描述
第一行为一个正整数 n n n 1 ≤ n ≤ 1 0 4 1{\le}n{\le}10^4 1n104),表示数字串的个数。

第二行给出 n n n个数字串( 1 ≤ 1{\le} 1每个串的长度 ≤ 9 {\le}9 9),用空格隔开。

输出描述
输出一个整数,表示能生成的最小整数(需要去掉前导0)。

样例1
输入
3
53 01 2
输出
1253
解释
按01、2、53的顺序拼接,能得到最小整数1253。

下面是对这道题的解答:

贪心策略: 要使整数最小,只需优先选取字典序较小的字符数串作为整数的高位,字典序较大的字符数串放在低位。
注意,排序函数cmp要写成 return a + b < b + a,若如此写,假设a=120,b=12;a+b=12012,b+a=12120,那么,120会排在12的前面;如果写成return a<b,这会使得120排在12的后面,为了使得形成的整数串最小,120必须作为高位,12作为低位,因为12012<12120。
:对于cmp函数,当return true时,string a排在string b的前面;当return false时,string a排在string b后面。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;

bool cmp(string a, string b) { //比较函数,字符串按字典序从小到大排序
    return a + b < b + a;
}

int main() {
	int n, i, j;
	string s[10005];
	string str;
	cin >> n;
	getchar();
	for (i = 0; i < n; i++) {
		cin >> s[i];
	}
	sort(s, s + n,cmp); //对数字串按字典序从小到大排序

	for (i = 0; i < n; i++) {
		str.append(s[i]); //将排序后的数字串按序写入,形成整数串
	}
	i = 0;
	while (str[i] == '0' &&  i<str.length()) i++; //去除前导0
	if (i==str.length()) {//如果整数串全是0,现在已经成空串了
		cout<<"0"; //整数为0
	} else {
		for (j = i; j < str.length(); j++) { //输出去除前导0的整数
			cout << str[j];
		}
	}
	cout << endl;
	return 0;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《算法艺术与信息学竞赛题解pdf》是一本介绍算法艺术和信息学竞赛题解的教材。它通过详细解析一系列典型的竞赛题目,讲解了如何使用不同的算法数据结构来解决这些问题。 在这本教材中,作者首先介绍了算法和信息学竞赛的基本知识,包括常用的数据结构算法思想。然后,他通过具体的例子和题目,展示了如何应用这些知识来解决实际问题。每个题目都有详细的解析过程,包括问题的分析、算法的设计和优化等内容。 这本教材的特点之一是注重实践。作者通过大量的实例和练习题,帮助读者巩固所学的知识,并掌握解决问题的方法。此外,他还提供了一些常见的竞赛技巧和经验,帮助读者在竞赛中取得好的成绩。 《算法艺术与信息学竞赛题解pdf》适合对算法和信息学竞赛感兴趣的读者。无论是初学者还是有一定基础的读者,都可以从中受益。通过学习这本教材,读者不仅可以提高解决问题的能力,还可以培养逻辑思维和计算机编程的能力。 总之,这本教材提供了一种全面的学习算法和信息学竞赛的方式。通过深入浅出的讲解和丰富的实例,它帮助读者建立起坚实的算法基础,提高解决问题的能力,并在竞赛中取得优异的成绩。 ### 回答2: 算法艺术与信息学竞赛题解pdf是一本以算法和信息学竞赛题为内容的电子书,提供了有关这些题目的详细解答。该书的出版旨在帮助读者更好地理解和掌握算法和信息学竞赛的核心知识和技巧。 首先,这本书介绍了一些常见的算法数据结构,如贪心算法、动态规划、图论等。通过逐一解析题目,并给出相应的算法设计和实现思路,读者可以学习到不同类型题目的解题方法和技巧。 其次,该书还强调了对问题进行建模的重要性。在解决问题时,合理的问题建模可以将问题转化为更易于理解和求解的形式。书中通过具体的例子,教给读者如何抽象问题,构建合适的数据结构来解决实际问题。 此外,该书还提供了大量典型题目的详细解答,包括解题思路、具体实现和代码示例等。读者可以通过参考这些题目的解答,了解不同类型题目的解题思路,提高自己的解题能力。 总之,算法艺术与信息学竞赛题解pdf是一本帮助读者提高算法和信息学竞赛能力的实用电子书。通过学习其中的知识和技巧,读者可以更好地解决相关问题,并在竞赛中获得优异成绩。 ### 回答3: 《算法艺术与信息学竞赛题解PDF》是一本内容丰富的电子书,主要讲解了算法艺术和信息学竞赛中常见的题目解法。该书以清晰简洁的语言,详细介绍了解题思路和具体实现过程。 这本电子书中涵盖了多个题型,包括排列组合、图论、动态规划、贪心算法等。通过这些经典的题目,读者可以了解到不同算法在解决问题时的特点和应用场景,提升算法设计和编程能力。 该电子书特色之一是讲解了信息学竞赛中被广泛使用的算法数据结构,如并查集、最短路径算法、网络流等。阅读该书可以让读者对这些常用的算法有更深入的理解,从而在解决实际问题时能够选择适当的算法。 此外,该电子书为了方便读者理解,还提供了大量的实例,以演示不同算法的具体应用。这些实例不仅帮助读者掌握算法的思维方式,还能够培养读者的问题分析和解决能力。 总之,《算法艺术与信息学竞赛题解PDF》是一本非常实用的电子书,适合对算法和信息学竞赛感兴趣的读者。通过阅读该书,读者可以提高解题速度和准确度,增强算法设计和编程能力,对解决问题的思路和方法有更深入的认识。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值