贪心入门
贪心概念
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
贪心算法的使用前提; 局部最优解一定能导致全局最优解。
接下来我们一起看几道简单的贪心类型的题目吧!
没错
简单贪心问题
排队接水(water.cpp)
展开
题目描述
有 n 个人在一个水龙头前排队接水,假如每个人的接水的时间为 Ti,请编程找出一种 n 个人排队的顺序,使得 n 个人的平均花费时间最小。
假设下一个人接着打水的这个切换过程不消耗时间,且注意,每个人的花费时间包括自己接水的时间和排队等待接水的时间。
输入格式
第一行一个整数 n,1≤n≤1000。
第二行分别为每个人的接水时间 1,2,...,T1,T2,...,Tn,用空格隔开,Ti≤106。
输出格式
第一行为最优的排队顺序,即编号 1∼�1∼n 的一种排列,每两个数字之间用空格隔开。
第二行为这种排列方案下的平均花费时间(保留两位小数)。
样例
输入#1
10
56 12 1 99 1000 234 33 55 99 812
Copy
输出#1
3 2 7 8 1 4 9 6 10 5
532.00
Copy
数据范围
【算法分析】
由于总打水时间=打水时间+等待时间,打水时间无法改变,只能减少等待时间,所以将打水时间越小的排在越前面,后面的人等待时间就越短,总的等待时间就越小,平均等待时间就越短。所以这道题可以用贪心法解答,基本步骤:(1)将输入的时间按从小到大排序;(2)将排序后的时间按顺序依次放入每个水龙头的队列中;(3)统计,输出答案。s[i]:第i个人的总花费时间, a[i]:自己的打水时间s[1]=a[1]+0(第一个人不用等待);s[2]=a[2] (自己的打水时间)+a[1](自己的等待时间)= a[2] + s[1];s[3]=a[3] (自己的打水时间)+( a[2]+a[1] )(自己的等待时间)= a[3] + s[2];s[4]=a[4] (自己的打水时间)+( a[3]+a[2]+a[1])(自己的等待时间)= a[4] + s[3];可以推导出:s[i]=a[i]+s[i-1];
重点:如何构造结构体,需要时间跟编号!
#include<bits/stdc++.h>
using namespace std;
struct dashui{
double bh,time;
}a[1010];
bool cmp(dashui s1,dashui s2){//打水时间越短的越靠前
return s1.time<s2.time;
}
int main(){
double s[1010]={0};
int n,i;
double sum=0;
cin >> n;
for(i=1;i<=n;i++){
cin >> a[i].time ;
a[i].bh = i;
}
sort(a+1,a+n+1,cmp);//按照打水时间排序
for(i=1;i<=n;i++){
s[i]= a[i].time + s[i-1];//每个人的总时间=自己打水时间+等待时间
sum += s[i];
}
for(i=1;i<=n;i++){
cout << a[i].bh << " ";
}
cout << endl;
cout<< fixed << setprecision(2)<< sum/n;
}
最小等待时间(waiting)
展开
题目描述
超市的收银处有n 位顾客在排队等着付款,他们的编号依次为 1,2,...,n。由于每个顾客所购的商品不同,因此付款时所需的等待时间也就不一样。给出这 n 个人每个人单独付款所需的时间。而顾客不同的付款顺序,所有顾客总等待时间是不一样的,收银员想知道所有顾客总等待时间最少是多少。每个人的等待时间在这里指的是排队等待付款的时间。
输入格式
共 22 行,第一行为正整数 n,表示排队的人数;第二行 n 个由空格分隔的正整数,分别为这 n 个人单独付款所需的时间。
输出格式
共 11 行 11 个数,表示所有顾客总的最少等待时间。
样例
输入数据#1
4
1 2 1 2
Copy
输出数据#1
7
Copy
数据范围/约定
1≤n≤100,每位顾客单独付款所需的时间≤150
【算法分析】对比一下第一题,其实这两题思路完全一样,而且这道市赛题要比上一题简单很多。那么本题跟上面一题的联系跟区别又是什么呢?
#include<bits/stdc++.h>
using namespace std;
int main() //不需要结构体
{
int a[105]={0},n,i,s[105]={0},sum=0;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>a[i];
}
sort(a+1,a+n+1);
for(i=1;i<=n;i++)
{
s[i]=s[i-1]+a[i-1];
sum += s[i]; //求和也不一样,这一点区别很大
}
cout<<sum;
}
3. 抓鱼
/ TopsCoding / 题库 /
抓鱼
展开
题目描述
五一节放假了,小仪高高兴兴地去奶奶家玩。在去奶奶家的路上,有n个小池塘,池塘很浅很浅,所以没有危险的。每个池塘中有若干条鱼。抓同一个池塘中的每条鱼的时间是相同的,但抓不同池塘中的鱼的时间可能不同。
为了不让奶奶久等,小仪只有t分钟的时间能用来抓鱼。请问小仪最多能抓多少条鱼呢?
输入格式
输入文件fish.in的第一行有二个整数n和t,表示有n个池塘,小仪能使用t分钟时间抓鱼。n和t之间以一个空格分隔。
第二行,有n个正整数,第i个正整数xi表示第i个池塘中有xi条鱼(每二个正整数之间有一个空格)。
第三行,有n个以空格分隔的正整数,第i个正整数yi表示第i个池塘中每抓一条鱼都需要yi分钟时间(每二个正整数之间有一个空格)。
输出格式
输出文件fish.out中只有一行,该行只有一个整数v,表示小仪最多能抓v条鱼。
样例
输入#1
3 26
2 1 3
4 5 6
Copy
输出#1
5
Copy
解释#1
小仪可以花8分钟在第1个池塘抓2条鱼,花5分钟时间在第2个池塘抓1条鱼,花12分钟时间在第3个池塘抓2条鱼。
小仪最多可以抓5条鱼。
数据范围
20%的数据,1≤n≤3;
80%的数据,1≤n≤1000;
100%的数据,1≤n≤100000,1≤t≤5000000000。
【算法分析】贪心策略,先抓鱼时间少的,把抓鱼的时间和每个鱼塘里面鱼的只数作为一个整体进行排序,因此联想到用结构体排序。
#include<bits/stdc++.h>
using namespace std;
struct zy{
long long a,b;
}s[100000];
long long cmp(zy x,zy y){
return x.b<y.b;
}
int main(){
long long int i,j,n,m,sum=0;
cin>>n>>m;
for(i=1;i<=n;i++){
cin>>s[i].a;
}
for(i=1;i<=n;i++){
cin>>s[i].b;
}
sort(s+1,s+n+1,cmp);//排序,排完了开始抓鱼
for(i=1;i<=n;i++){//一个池塘一个池塘的抓鱼
for(j=1;j<=s[i].a;j++){//循环的范围是j<=s[i].a,因为可以一条一条的抓,直到这个池塘没有鱼或者时间不够了
if(m>=s[i].b){
sum++;
m=m-s[i].b;
}
}
}
cout<<sum;
}
4. 月饼问题
题目描述
月饼是中国人在中秋佳节时吃的一种传统食品,不同地区有许多不同风味的月饼。现给定所有种类月饼的库存量、总售价、以及市场的最大需求量,请你计算可以获得的最大收益是多少。
注意:销售时允许取出一部分库存。样例给出的情形是这样的:假如我们有 33 种月饼,其库存量分别为 1818 、 1515 、 1010 万吨,总售价分别为 7575 、 7272 、 4545 亿元。如果市场的最大需求量只有 2020 万吨,那么我们最大收益策略应该是卖出全部 1515 万吨第 22 种月饼、以及 55 万吨第 33 种月饼,获得 72+45/2=94.572+45/2=94.5 (亿元)。
输入格式
每个输入包含 11 个测试用例。
每个测试用例先给出一个不超过 10001000 的正整数 N 表示月饼的种类数、以及不超过 500500 (以万吨为单位)的正整数 D 表示市场最大需求量。
随后一行给出 N 个 正数 表示每种月饼的库存量(以万吨为单位);
最后一行给出 N 个 正数 表示每种月饼的总售价(以亿元为单位)。数字间以空格分隔。
输出格式
对每组测试用例,在一行中输出最大收益,以亿元为单位并精确到小数点后 22 位。
样例
输入数据#1
3 20
18 15 10
75 72 45
Copy
输出数据#1
94.50
【算法分析】输出最大收益,则贪心策略是先卖单价最贵的。18、15、10万吨,总售价分别为75、72、45亿元,则它们的单位售价为75/18=4.17、72/15=4.8、45/10=4.5,因此应该先卖第二种,在卖第三种,最后卖第一种。因此需要把总库存跟总售价作为一个整体排序,因此联想到应该用结构体排序。
#include <bits/stdc++.h>
using namespace std;
struct yb {
double kc,sj,pj; //库存,售价,平均价
}a[1005];
int cmp(yb a,yb b){
return a.pj>b.pj; //按平均值从高到低排序
}
int main(){
int n,i,j,m;
double s=0;
cin>>n>>m;
for(i=1;i<=n;i++){
cin>>a[i].kc;
}
for(i=1;i<=n;i++){//第一行代表库存,第二行代表售价,怎么可以放在一个循环里面呢?
cin>>a[i].sj;
}
for(i=1;i<=n;i++){
a[i].pj=1.0*a[i].sj/a[i].kc; //每一种月饼的均价
}
sort(a+1,a+n+1,cmp); //根据均价由高到低排序
for(i=1;i<=n;i++){
if(m>=a[i].kc){ //如果要买的多余库存
m=m-a[i].kc; //库存更新
s=s+a[i].sj; //利润加上这类月饼的售价
}else{
s=s+m*a[i].pj; //否则,利润加上数量*单价
break;
}
}
cout<<fixed<<setprecision(2)<<s;
}
5. 部分背包问题
题目描述
阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 N(N≤100) 堆金币,第 i 堆金币的总重量和总价值分别是 mi,vi(1≤mi,vi≤100)。阿巴有一个承重量为 �(�≤1000)T(T≤1000) 的背包,但并没办法将全部的金币都装进去。他想装走尽可能多价值的金币。所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变。请问阿里巴巴最多可以拿走多少价值的金币?
输入格式
第一行两个整数 N、T。
接下来 N 行,每行两个整数 mi,vi。
输出格式
一个整数表示答案,输出两位小数。
样例
输入#1
4 50
10 60
20 100
30 120
15 45
Copy
输出#1
240.00
Copy
数据范围
【算法分析】是不是很眼熟,其实这道题目跟月饼完全一样的解题思路。贪心策略应该为单位最大价值,即总价值/总重量=单位最大价值。先装单位价值最大的。
#include <bits/stdc++.h>
using namespace std;
struct beibao{
double zl;
double jz;
double pj; //构造结构体
}a[105];
bool cmp(beibao a,beibao b){
return a.pj>b.pj; //按平均从大到小排序
}
int main(){
int n,i,m;
double sum=0;
cin>>n>>m;
for(i=1;i<=n;i++){
cin>>a[i].zl>>a[i].jz;
a[i].pj=1.0*a[i].jz/a[i].zl; //跟月饼的输入不一样,关注一下
}
sort(a+1,a+n+1,cmp);
for(i=1;i<=n;i++){
if(m>=a[i].zl){
sum=sum+a[i].jz;
m=m-a[i].zl;
}else {
sum=sum+a[i].pj*m;
break;
}
}
cout<<fixed<<setprecision(2)<<sum;
}
活动安排问题
活动安排
题目描述学校在最近几天有n(n≤1000)个活动,这些活动都需要使用学校的大礼堂,在同一时间,礼堂只能被一个活动使用。由于有些活动时间上有冲突,学校办公室人员只好让一些活动放弃使用礼堂而使用其他教室。现在给出 n 个活动使用礼堂的起始时间和结束时间 ,请你帮助办公室人员安排一些活动来使用礼堂,要求安排的活动尽量多。请问最多可以安排多少活动?请注意,开始时间和结束时间均指的是整点时间(某个小时的0分0秒),如:3 5,指的是3:00~5:00,因此3 5和5 9这两个时间段不算冲突的时间段。输入格式第一行一个整数 n(n≤1000)接下来的 n 行,每行两个整数,第一个起始时间,第二个是结束时间(起始时间<结束时间<32767)输出格式输出最多能安排的活动数。
样例输入#
11
3 5
1 4
12 14
8 12
0 6
8 11
6 10
5 7
3 8
5 9
2 13
输出#
4
【算法分析】活动安排是一类非常常见的贪心类题目。例如教室的时间安排,演出的时间安排等等。那么诸如此类问题的贪心策略是什么呢?--我们希望留下来的时间尽可能的多,即每场活动结束的时间越早,那我们就有更多的时间安排其他的活动。因此我们首先对所有时间的结束时间进行从小到大的排序,例如在本题里面结束最早的时间应该是1,4,然后我们再从剩下的时间里面寻找下一个活动开始的时间大于等于当前时间的活动,这就是第二个活动,同时把这个活动的结束时间记录下来。
#include<bits/stdc++.h>
using namespace std;
struct hd{
int begin,end;
}a[1010];
bool cmp(hd s1,hd s2){
return s1.end <s2.end ;
}
int main(){
freopen("act.in","r",stdin);
freopen("act.out","w",stdout);
int n,i,be,c=1;
cin >> n;
for(i=1;i<=n;i++){
cin >> a[i].begin >> a[i].end;
}
sort(a+1,a+n+1,cmp);
be = a[1].end;
for(i=2;i<=n;i++){
if(a[i].begin >= be){
c++;
be = a[i].end ;
}
}
cout << c;
}
分组问题
1. 纪念品分组
题目描述
元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得 的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品, 并且每组纪念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。
你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。
输入格式
输入包含n+2行:
第1行包括一个整数w,为每组纪念品价格之和的上限;第2行为一个整数n,表示购来的纪念品的总件数
第3-n+2行每行包含一个正整数Pi (5 ≤ Pi ≤ w),表示所对应纪念品的价格。
输出格式
输出仅一行,包含一个整数, 表示最少的分组数目和 。
样例
输入数据#1
100
9
90
20
20
30
50
60
70
80
90
Copy
输出数据#1
6
Copy
说明/提示
50%的数据满足: 1 ≤n ≤ 15
100%的数据满足: 1 ≤ n ≤ 30000, 80 ≤ W ≤ 200
贪心策略是啥?想一想。
#include<bits/stdc++.h>
using namespace std;
int main(){
int w,n,a[30005],k,sum,s=0,i;
cin >> w >> n;
for(i=1;i<=n;i++){
cin >> a[i];
}
sort(a+1,a+n+1);
k=1;
sum=0;
for(i=n;i>=1;i--){
if(a[i]+a[k]<=w&&sum<n){
s++;
k++;
sum+=2;
} else if(a[i]+a[k]>w && sum<n){
s++;
sum++;
}
}
cout << s;
}
其他简单贪心问题
[USACO 1.3.2] Barn Repair 修理牛棚
展开
题目描述
在一个狂风暴雨的夜晚, farmer John 的牛棚的屋顶、门被吹飞了。 好在许多牛正在度假,所以牛棚没有住满。 牛棚一个紧挨着另一个被排成一行,牛就住在里面过夜。有些牛棚里有牛,有些没有。所有的牛棚有相同的宽度。自门遗失以后,farmerJohn 必须尽快在牛棚前竖立起新的木板,来挡住门口,防止牛跑掉。他的新木材供应商将会供应他任何他想要的长度,但是吝啬的供应商只能提供有限数目的木板。 farmer John 想将他购买的木板总长度减到最少。
现在给出:可能买到的木板最大的数目 M(1≤M≤50)、牛棚的总数S(1≤S≤200)、 牛棚里牛的总数 C(1≤C≤S) 和牛所在的牛棚的编号 stallnumber(1≤stallnumber≤S),计算拦住 所有有牛的牛棚 所需木板的最小总长度。
输出所需木板的最小总长度作为答案。
输入格式
第 11 行:木板最大的数目 M, 牛棚的总数 S 和牛的总数 C, 用空格分开。
第 22 到 C+1 行:每行包含一个整数,表示牛所占的牛棚的编号。
输出格式
单独的一行包含一个整数表示所需木板的最小总长度。
样例
输入数据#1
4 50 18
3
4
6
8
14
15
16
17
21
25
26
27
30
31
40
41
42
43
Copy
输出数据#1
25
【算法分析】解题思路:你以为贪心的题目都这么简单?那只能说你还是太年轻了。上面这道题,我打赌你连题目都看不懂,为什么最后的结果是25,是不是很懵逼?
贪心策略:首先拦住所有牛最蠢的方法就是从第一个拦到最后一个,但是这样浪费木材。现在有m个木板,如何最省木板呢?m个木板围住牛,会有m-1个空隙,那么我们只要是的m-1个空隙最大就可以了。怎么知道空隙呢?我们需要知道相邻两头牛之间的间隙,然后对相邻的两头牛做差值,再对差值进行排序就行了。你能想得到吗?
#include<bits/stdc++.h>
using namespace std;
bool cmp(int s1,int s2){
return s1 > s2;
}
int main(){
int m,s,c,a[210]={0},sum=0,k=0,i,b[210]={0},s1=0;
cin >> m >> s >> c;
for(i=1;i<=c;i++){
cin >> a[i];//输入没有门的牛棚编号
}
sort(a+1,a+c+1);
/*因为最多只有m个木板,就有m-1个空隙,所以为了节省总长度,所有的
空隙都应该用上,且应该用在牛棚之间空隙最大的那几个。*/
sum=a[c]-a[1]+1;//用一块木板做门,所需要的长度
for(i=1;i<c;i++){//求出牛棚之间的空隙
k++;
b[k]=a[i+1]-a[i]-1;
}
sort(b+1,b+k+1,cmp);//从大到小排出牛棚的空隙
for(i=1;i<=m-1;i++){
s1 = s1 + b[i]; //中间空出来的牛棚数量
}
cout << sum - s1;
}
结尾
这次只是简单贪心,下次会讲:进阶贪心
下期再见~~~~~~~~一键三连