关于贪心

贪心

目录 Content

  • 概述
  • 实现
  • 证明
  • 思考
  • 应用

Part 1 概述

贪心算法(英语:greedy algorithm),是用计算机来模拟一个「贪心」的人做出决策的过程。这个人十分贪婪,每一步行动总是按某种指标选取最优的操作。而且他目光短浅,总是只看眼前,并不考虑以后可能造成的影响。

可想而知,并不是所有的时候贪心法都能获得最优解,所以一般使用贪心法的时候,都要确保自己能证明其正确性。 OI-Wiki

应当说,贪心不是一种具体的算法,是一种思想。但是使用贪心思想的应用具有某种共同点,既贪心有几种常见的表现形式。

由于贪心并不从整体思考,所以贪心思想的使用有时是不正确的。贪心思想的正确性需要证明。

一个问题可以使用贪心,有一些条件:

  • 具有最优子结构性质

意思是问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。

  • 贪心选择性质

一个问题的整体最优解可通过一系列局部的最优解的选择达到,并且每次的选择可以依赖以前作出的选择,但不依赖于后面要作出的选择。这就是贪心选择性质。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解

其中,贪心选择性质是贪心正确的前提。

贪心与DP有着千丝万缕的联系。但是他们也有区别。

贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。

Part 2 实现

贪心没有固定的实现方式,但是几乎都有一个共性:

需要排序

Part 3 证明

贪心有两种常见的证明方式。

  1. 反证法:如果交换方案中任意两个元素/相邻的两个元素后,答案不会变得更好,那么可以推定目前的解已经是最优解了。
  2. 归纳法:先算得出边界情况的最优解 ,然后再证明:对于每个最优解,都可以由前面的最优解推导出结果。

但是实际上在实际做题时就能严格证明的情况很少。大部分还是要凭借经验和运气判断。

Part 4 思考

如何想出贪心算法呢?一般可以按照以下步骤思考:

  1. 建立数学模型来描述问题 。
  2. 把求解的问题分成若干个子问题 。
  3. 对每个子问题求解,得到子问题的局部最优解 。
  4. 把子问题的解局部最优解合成原来解问题的一个解

还有一些常见的问题类型可以使用贪心,如果在问题可以转化为或抽离出这些问题也可以考虑贪心:

  • 选择排序
  • 平衡字符串
  • 买卖股票的最佳时机
  • 跳跃游戏
  • 钱币找零
  • 多机器调度问题
  • 举办活动数量最多
  • 无重叠区间
  • 求某最值

Part 5 应用

例1 区间选点

请编程完成以下任务:

  1. 读取闭区间的个数及它们的描述;
  2. 找到一个含元素个数最少的集合,使得对于每一个区间,都至少有一个整数属于该集合,输出该集合的元素个数。

首先,可以画图分析:


但是交界区间那么大,选哪个较好呢。

首先,我们不知道这个区间会不会被其他区间再次覆盖。但是显然,我们越放得靠后,后面的区间更有可能可以覆盖。

所以我们尽量往后放。

#include<bits/stdc++.h>
using namespace std;
struct node{
	int l,r;
}s[10005];
bool cmp(node a,node b){
	if(a.l!=b.l) return a.l<b.l;
	else return a.r<b.r;
}
int n,ans=1;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>s[i].l>>s[i].r;
	sort(s+1,s+1+n,cmp);
	int lst=s[1].r;
	for(int i=1;i<=n;i++){
//		printf("Last%d [%d,%d]\n",lst,s[i].l,s[i].r);
		if(s[i].l>lst){
			ans++;
			lst=s[i].r;//放右节点
		}
	}
	cout<<ans;
}

例2 凌乱的yyy

现在各大 oj 上有 nn 个比赛,每个比赛的开始、结束的时间点是知道的。
yyy 认为,参加越多的比赛,noip 就能考的越好(假的)。
所以,他想知道他最多能参加几个比赛。
由于 yyy 是蒟蒻,如果要参加一个比赛必须善始善终,而且不能同时参加 22 个及以上的比赛。

这道题我也没法解释地比kkksc03更好:

在一个数轴上有n条线段,现要选取其中k条线段使得这k条线段两两没有重合部分,问最大的k为多少。

最左边的线段放什么最好?

显然放右端点最靠左的线段最好,从左向右放,右端点越小妨碍越少

其他线段放置按右端点排序,贪心放置线段,即能放就放

#include<bits/stdc++.h>
using namespace std;
int n;
struct node{
	int l,r;
}s[1000005];
int ans=0;
bool cmp(node a,node b){
	return (a.r)<(b.r);
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>s[i].l>>s[i].r;
	sort(s+1,s+1+n,cmp);
	int lst=0;
	for(int i=1;i<=n;i++){
		if(s[i].l>=lst) ans++,lst=s[i].r;
	}
	cout<<ans;
}

例3 合并果子

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

例如有3种果子,数目依次为1,2,9。可以先将 1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为 12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。

为什么会有最小值呢?

因为如果我们一直操作重量大的堆,那么就会浪费许多体力。

因此我们要尽量操作重量小的堆。所以可以使用小根堆,每次把第一第二的元素合并起来塞进堆。

#include<bits/stdc++.h>
using namespace std;
priority_queue<int,vector<int>,greater<int> > q;
int n,ans;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		int tmp;cin>>tmp;
		q.push(tmp);
	}
	if(n==1){
		cout<<0;
		return 0;	
	}
	while(q.size()>1){
		int q1=q.top();
		q.pop();
		int q2=q.top();
		q.pop();
		q.push(q1+q2);
		ans+=q1+q2;
	}
	cout<<ans;
}

例4 奶牛杂耍

每头牛都有自己的体重以及力量,编号为 ii 的奶牛的体重为 wi,体力si

当某头牛身上站着另一些牛时它就会在一定程度上被压扁,我们不妨把它被压扁的程度叫做它的压扁指数。对于任意的牛,她的压扁指数等于摞在她上面的所有奶牛的总重(当然不包括她自己)减去它的力量。奶牛们按照一定的顺序摞在一起后, 她们的总压扁指数就是被压得最扁的那头奶牛的压扁指数。

你的任务就是帮助奶牛们找出一个摞在一起的顺序,使得总压扁指数最小。

我们尝试再次套用排序时会发现,这道题有两个数据都对答案有影响。

于是可以尝试先分解两个最小的情况:

设wa + sa > wb + sb且a与b相邻

a在上面,b在下面时
那么a和b上面的牛总重为w

a的压扁指数为w - sa b为w + wa - sb

因为wa + sa > wb + sb

那么wa - sb > wb - sa

因为wb >= 0,所以wa - sb > - sa

所以w + (wa - sb) > w - (sa),所以当a在上时,总压扁指数为w + wa - sb(不考虑上面的牛的压扁指数)

b在上面,a在下面时
a -> w + wb - sa

b -> w - sb

因为wa + sa > wb + sb

所以wb - sa < wa - sb

又因为wa > 0

所以wb - sa < -sb

所以w + (wb - sa) < w + (-sb),所以当b在上时,总压扁指数为w - sb(不考虑上面的牛的压扁指数)

综上
当a在上时w + wa - sb

b在上时 w - sb

所以力量加体重大(w + s 大)的在下面比在上面更优

题解:@liuzitong

Code:

#include<bits/stdc++.h>
using namespace std;
int n;
struct cows{
	int w,s;
}c[50005];
bool cmp(cows a,cows b){
	return (a.w+a.s)<(b.w+b.s);
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>c[i].w>>c[i].s;
	sort(c+1,c+1+n,cmp);
	int sum=0,ans=INT_MIN;
	for(int i=1;i<=n;i++){
		ans=max(ans,sum-c[i].s);
		sum+=c[i].w;
	}
	cout<<ans;
}

EOF

感谢观看。QwQ

posted @   haozexu  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示