CHAPTER_4 算法初步入门
4.4.1 简单贪心
贪心算法:对于一个较大的问题,把复杂的问题划分为多个小问题。并且对于每个子问题的解进行选择,找出最优值,进行处理,再找出最优值,再处理。也就是说贪心算法是一种在每一步选择中都采取在当前状态下最好或最优的选择,从而希望得到结果是最好或最优的算法。
因此贪心算法的核心是数学问题,对于每个不同的题目,需要从数学证明中找到最优的策略。
题目1:
现有一批不同种类的月饼。给定所有种类月饼的库存量、总售价和市场最大需求量,请计算这批月饼可获得的最大收益。
如:有A、B、C三种月饼,库存量分别为18、15、10万吨,总售价分别为75、72、45亿元,市场最大需求量为20万吨。那么最优策略是卖出15万吨B和5万吨C,获得总收益72+45/2=94.5亿元。
输入格式:
每次输入包含一个测试用例。每个测试用例先给出月饼总数N(N<1000)和市场最大需求量D(D<500);随后一行给出N种月饼的库存量;最后一行给出每种月饼的总售价。
输出格式:
输出最大收益 ,以亿元为单位(保留小数点后两位)。
输入样例:
3 20
18 15 10
75 72 45
输出样例:
94.50
思路:
采用“总是选择单价最高的月饼出售”的策略
Step1——对于每种月饼,计算单价=总售价/总库存,并将所有月饼按单价从高到底排序
Step2——月饼按单价从高到低枚举:如果当前月饼库存量<=需求量,则将月饼全部卖出,并将需求量减少、收益增加该月饼总售价;如果当前月饼库存量>需求量,则卖出需求量数额的月饼,并将库存量置0、收益增加该月饼单价*需求量
Step3——需求量为0时退出,控制格式输出收益
参考代码:
#include<cstdio>
#include<malloc.h>
#include<algorithm>
using namespace std;
typedef struct yuebin{
double num;
double total_price;
double each_price;
}yb; //定义月饼的库存、总售价、单价
bool cmp(yb a,yb b){
if(a.each_price>=b.each_price)
return true;
else
return false;
}
int main(){
int n;
double d,sum=0;
scanf("%d%lf",&n,&d); //n为月饼种类数,d为市场总需求量 ,sum为收益(初始置0)
yb *p=(yb *)malloc(sizeof(yb)*n); //申请n个连续空间存放n个月饼
for(int i=0;i<n;i++)
scanf("%lf",&p[i].num);
for(int i=0;i<n;i++){
scanf("%lf",&p[i].total_price);
p[i].each_price=p[i].total_price/p[i].num;
} //输入每个月饼的库存、总价,并计算单价
sort(p,p+n,cmp); //月饼按单价排序
for(int i=0;i<n&&d>0;i++){
if(p[i].num<=d){
sum=sum+p[i].total_price;
d=d-p[i].num;
}
else{
sum=sum+p[i].each_price*d;
d=0;
}
} //按上述策略循环,当d=0或者遍历完所有种类月饼时退出
printf("%.2lf",sum);
return 0;
}
题目2:
给定数字0~9若干个。可以任意顺序排列这些数字,但必须全部使用。目标是使得得到的数尽可能小。
现给定数字,请编写程序输出其能组成的最小数。
如:给定两个0、两个1、三个5和一个8,所得最小数字为10015558。
输入格式:
每个输入包含一个测试用例。每个测试用例在一行中给出10个非负整数,从第一个到第十个顺序表示数字0、数字1、...、数字9的个数。输入数字间用空格分割,0~9数字总个数小于50,且至少有一个非0数字。
输出格式:
在一行中输出所组成的最小数
输入样例:
2 2 0 0 0 3 0 0 1 0
输出样例:
10015558
思路:
先从1~9中选择不为0的最小数字输出(即生成数字的第一位要最小,但不能为0),然后从0~9输出数字,每个数字输出次数为其剩余的个数
参考代码:
#include<iostream>
using namespace std;
int main(){
int p[10]={0};
for(int i=0;i<10;i++)
cin>>p[i]; //控制格式输入10个数
for(int i=1;i<10;i++)
if(p[i]){
cout<<i;
p[i]--;
break;
} //寻找第一个不为0的数字,输出后将其个数减一并退出
for(int i=0;i<10;i++)
for(int j=0;j<p[i];j++)
cout<<i;
cout<<endl; //剩下数字由小到大将其全部输完
return 0;
}
4.4.2 区间贪心
题目1:
区间不相交问题——给出N个开区间(x,y),从中选出尽可能多的开区间,使这些开区间两两没有交集。
如:开区间(1,3)、(2,4)、(3,5)、(6,7),可以选出最多3个区间(1,3)、(3,5)、(6,7)他们间互相没有交集。
思路:
区间选择的时候,如果存在大区间完全包含小区间的情况,那么最佳策略肯定是选择内部的小的区间。
因此,在去除区间包含情况后,我们对所有区间按左端点x从大到小排序,一定有右端点y1>y2>...>yn成立。如下图,我们观察左端点最大的区间L1时可以发现,L1右边一定存在一断不与之前区间重叠的部分。假若我们忽视掉这多出的部分,那么L1则被L2区间包含了,按照上面的原则我们要选取被包含的L1。
分析完上面子问题,我们可以确定策略:总是先选择左端点最大的区间。
参考代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100;
struct node {
int x,y;
}p[maxn];
bool cmp(node a, node b) {
if(a.x != b.x)
return a.x > b.x;
else
return a.y < b.y;
} //优先选择左端点大的,如果左端点一样大选择右端点小的(选择被包含的区间)
int main() {
int n;
while(cin>>n, n != 0) {
for(int i = 0; i < n; i++)
cin>>p[i].x>>p[i].y;
sort(p, p + n, cmp);
int ans = 1, lastx = p[0].x; // ans记录不相交区间个数,lastx记录上一次选择区间的左端点
for(int i =1; i < n; i++) {
if(p[i].y <= lastx) {
lastx = p[i].x;
ans++;
}
} //选择左端点最大并且右端点离lastx最近的区间,选择完毕重新定位lastx,结果加一
cout<<ans<<endl;
}
return 0;
}