贪心法--汽车加油问题,硬币问题,喷水装置,会场安排问题,独木舟上的旅行,阶乘之和

 

贪心算法基本思想:

贪心算法总是做出在当前看来是最好的选择,并不会从总体去最优考虑。虽然贪心算法不会对所有问题找到最优,但是有时候会得到最优解的近似解。

贪心算法的基本要素:

1,贪心选择性质:指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。(这是贪心算法和动态规划的主要区别)

2,最优子结构:当一个问题包含其子问题的最优解是称此问题具有最优子结构性质。
 

以上源自https://wenku.baidu.com/view/21a0784cdd36a32d73758188.html

下面将给出几个利用贪心算法解决的题目。

1.汽车加油问题

题目描述:

一辆汽车加满油后可行驶n公里。旅途中有若干个加油站。设计一个有效算法,指出应
在哪些加油站停靠加油,使沿途加油次数最少。对于给定的n(n <= 5000)和k(k <= 1000)个加油站位置,编程计算最少加油次数。并证明算法能产生一个最优解。
要求:
输入:第一行有2个正整数n和k,表示汽车加满油后可行驶n公里,且旅途中有k个加油站。接下来的1 行中,有k+1 个整数,表示第k个加油站与第k-1 个加油站之间的距离。第0 个加油站表示出发地,汽车已加满油。第k+1 个加油站表示目的地。
输出:输出编程计算出的最少加油次数。如果无法到达目的地,则输出”NoSolution”。

输入:

7 7

1 2 3 4 5 1 6 6

输出:

4

思路:
汽车行驶过程中,应走到自己能走到并且离自己最远的那个加油站,在那个加油站加油后再按照同样的方法贪心

具体算法:
先检测各加油站之间的距离,若发现其中有一个距离大于汽车加满油能跑的距离,则输出no solution
否则,对加油站间的距离进行逐个扫描,尽量选择往远处走,不能走了就让num++,最终统计出来的num便是最少的加油站数

以上转载自https://blog.csdn.net/pengluer/article/details/3455642

上图源自https://wenku.baidu.com/view/21a0784cdd36a32d73758188.html

/*输入:
7 7
1 2 3 4 5 1 6 6
输出:
4
*/
#include<iostream>
using namespace std;

const int maxn=1005;
int a[maxn];

void greedy(int a[],int n,int k)
{
	for(int i=0;i<k;i++)
	{
		if(a[i]>n)
		{
			cout<<"NoSolution"<<endl;
			return;
		}
	}
	int s=0;
	int sum=0;
	for(int i=0;i<k+1;i++)
	{
		s+=a[i];//加了某段距离就相当于走了某段,先假设能走某段 
		if(s>n)//s>n说明走了的大于能走的,则其实不能走这段 
		{
			sum++;//不能走这段,则表示应在这段距离的起始加油站加油
			s=a[i];//s清为这段距离
			cout<<"第"<<sum<<"次加油的位置是第:"<<i<<"个加油站"<<endl; 
		} 
	}
	cout<<sum<<endl;
}
int main()
{
	int n,k;
	cin>>n>>k;
	for(int i=0;i<k+1;i++)//注意有k个加油站,但有k+1段距离!这里是i<k+1而不是i<k
	{
		cin>>a[i];
	}
	greedy(a,n,k);//当然也可以不写子函数调用,而是直接写在主函数里
	return 0;
}

2.硬币问题

问题描述: 
有1元、5元、10元、50元、100元、500元的硬币各C1,C5,C10,C50,C100,C500枚。现在要用这些硬币来支付A元,最少需要多少枚硬币?假设本题至少存在一种支付方案。 
限制条件: 
0<=C1,C5,C10,C50,C100,C500<=10的9次方 
0<= A <= 10的9次方 
输入:

620

3 2 1 3 0 2
(C1 = 3 ,C2 = 2 ,C10 = 1 ,C50 = 3 ,C100 = 0 ,C500 = 2,A = 620 )
输出: 
6(500元硬币1枚,50元硬币2枚,10元硬币1枚,5元硬币2枚,合计6枚)

思路:为了尽量地减少硬币的数量,我们首先得尽可能多地使用500元硬币,剩余部分尽可能多地使用100元硬币,剩余部分尽可能多地使用50元硬币,剩余部分尽可能多地使用10元硬币,再剩余部分尽可能多地使用5元硬币,最后的部分使用1元硬币支付。 
贪心策略:优先使用大面值的硬币。

/*输入:
620
3 2 1 3 0 2
输出:
6 
*/
#include<iostream>
#include<algorithm> 
using namespace std;
int v[6]={1,5,10,50,100,500};//面值数组 
int A;
int c[6];//各种面值的硬币数量数组 
int main()
{
	cin>>A;
	for(int i=0;i<6;i++)
	{
		cin>>c[i];
	}
	int res=0;
	for(int i=5;i>=0;i--)//从最大的金额开始遍历 
	{
		int num=min(A/v[i],c[i]);//A/v[i]即尽量用的大金额的硬币数量,但还要考虑已有的硬币数量
		A-=num*v[i];
		res+=num;
	}
	cout<<res<<endl; 
	return 0;
}

3.喷水装置(一) NYOJ题目6

时间限制:3000 ms  |  内存限制:65535 KB
难度:3
描述
现有一块草坪,长为20米,宽为2米,要在横中心线上放置半径为Ri的喷水装置,每个喷水装置的效果都会让以它为中心的半径为实数Ri(0<Ri<15)的圆被湿润,这有充足的喷水装置i(1<i<600)个,并且一定能把草坪全部湿润,你要做的是:选择尽量少的喷水装置,把整个草坪的全部湿润。
输入:
第一行m表示有m组测试数据
每一组测试数据的第一行有一个整数数n,n表示共有n个喷水装置,随后的一行,有n个实数ri,ri表示该喷水装置能覆盖的圆的半径。
输出:
输出所用装置的个数
样例输入
2
5
2 3.2 4 4.5 6 
10
1 2 3 1 2 1.2 3 1.1 1 2
样例输出
2
5

贪心策略:最大半径的喷水装置优先。对半径排序,尽量选择半径大的。局部最优——整体最优

 

/*输入: 
2
5
2 3.2 4 4.5 6 
10
1 2 3 1 2 1.2 3 1.1 1 2
输出:
2
5
*/
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;

const int maxn=605;

double len(double R,double b)
{
	return sqrt(R*R-b*b);
}
int main()
{
	int m;
	cin>>m;
	while(m--)
	{
		double chang=20,kuan=2;
		vector<double> rs;
		int n;
		cin>>n;
		double R;
		while(n--)
		{
			cin>>R;
			rs.push_back(R);//将半径们放到不定长数组vector中 
		}
		sort(rs.begin(),rs.end(),greater<double>());//对vector中的元素从大到小排序 
		double sum=0;
		int i=0;
		while(chang>0)
		{
			chang-=2*len(rs[i],kuan/2);
			i++;
		}
		cout<<i<<endl;
	}
	return 0;
} 

另一种解法见https://blog.csdn.net/liujiuxiaoshitou/article/details/69728714

4.会场安排问题 NYOJ题目14

时间限制:3000 ms  |  内存限制:65535 KB
难度:4
描述
学校的小礼堂每天都会有许多活动,有时间这些活动的计划时间会发生冲突,需要选择出一些活动进行举办。小刘的工作就是安排学校小礼堂的活动,每个时间最多安排一个活动。现在小刘有一些活动计划的时间表,他想尽可能的安排更多的活动,请问他该如何安排。
输入
第一行是一个整型数m(m<100)表示共有m组测试数据。
每组测试数据的第一行是一个整数n(1<n<10000)表示该测试数据共有n个活动。
随后的n行,每行有两个正整数Bi,Ei(0<=Bi,Ei<10000),分别表示第i个活动的起始与结束时间(Bi<=Ei)
输出
对于每一组输入,输出最多能够安排的活动数量。
每组的输出占一行
样例输入
2
2
1 10
10 11
3
1 10
10 11
11 20
样例输出
1
2
提示:
注意,如果上一个活动在t时间结束,下一个活动最早应该在t+1时间开始
贪心策略:对结束时间排序,优先选择结束时间早的。局部最优——整体最优 

/*输入 
3
2
1 10
10 11
3
1 10
10 11
11 20
4
1 10
10 12
12 20
18 23
输出:
1
2
2 
*/
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

struct act{
	int begin;
	int end;
};
bool paixurule(act a,act b)//定义排序规则,对不定长数组vector中的所有活动按结束时间早晚排序,越早越在前面 
{
	return a.end<b.end;
}
bool cmp( act a,act b)
{
	return a.end<b.end;
} 
int main()
{
	int m;
	cin>>m;
	while(m--)
	{
		int n;
		cin>>n;
		vector<act> vt;
		while(n--)
		{
			act a;
			cin>>a.begin>>a.end;
			vt.push_back(a);
		}
		sort(vt.begin(),vt.end(),paixurule);//对不定长数组vector按结束时间排序 
		int count=vt.size();
		int k=0;//k用来存储安排好了的最后一段,初始化为第一段(第一段结束时间最早,肯定安排)
		for(int i=1;i<vt.size();i++)//从第二段开始遍历(第一段结束时间最早,肯定安排) 
		{
			if(vt[i].begin<=vt[k].end)
			{
				count--;//若当前段的起始时间<=已安排段的结束时间,则当前段i(即活动)不能安排,for下一段 
			}
			else
			{
				k=i;//若当前段的起始时间>已安排段的结束时间,则当前段i(即活动)可以安排,k变为安排好了的最后一段 
			} 
		}
		cout<<count<<endl;
	}
	return 0;
} 

5.独木舟上的旅行

时间限制:3000 ms  |  内存限制:65535 KB
难度:2
描述
进行一次独木舟的旅行活动,独木舟可以在港口租到,并且之间没有区别。一条独木舟最多只能乘坐两个人,且乘客的总重量不能超过独木舟的最大承载量。我们要尽量减少这次活动中的花销,所以要找出可以安置所有旅客的最少的独木舟条数。现在请写一个程序,读入独木舟的最大承载量、旅客数目和每位旅客的重量。根据给出的规则,计算要安置所有旅客必须的最少的独木舟条数,并输出结果。

输入
第一行输入s,表示测试数据的组数;
每组数据的第一行包括两个整数w,n,80<=w<=200,1<=n<=300,w为一条独木舟的最大承载量,n为人数;
接下来的一组数据为每个人的重量(不能大于船的承载量);
输出
每组人数所需要的最少独木舟的条数。

样例输入

3
85 5
84 85 80 84 83
90 3
90 45 60
100 5
50 50 90 40 60

样例输出

5
3
3

算法分析:

先把各个人的体重排序,然后计算最重的人和最轻的人能否同乘一条舟,如果不能,则最重的人就要单独乘坐一条舟,再求最轻的和第二重的人的和,依次比较。

/*样例输入:
3
85 5
84 85 80 84 83
90 3
90 45 60
100 5
50 50 90 40 60
样例输出:
5
3
3*/
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
	int m;
	cin>>m;
	while(m--)
	{
		int w,n;
		cin>>w>>n;
		int a[305];
		for(int i=0;i<n;i++)
		{
			cin>>a[i];
		}
		sort(a,a+n);//从小到大排列,若从大到小,则sort(a,a+n,greater<int>()); 
		for(int i=0;i<n;i++)
		{
			cout<<a[i]<<" ";
		}
		cout<<endl;
		int s=0,e=n-1;
		int res=0;//独木舟数量
		while(s<e)
		{
			if(a[s]+a[e]>w)//当前最轻的人和最重的人不能乘坐一个舟 
			{
				res++;
				e--;
			} 
			else//当前最轻的人和最重的人不能乘坐一个舟
			{
				res++;
				s++;
				e--;
			}
		}
		if(s==e)//跳出while循环是因为s>=e。若s和e指向同一个,则这个人要乘一个舟。 若s>e,直接结束就行 
			res++;		
		cout<<res<<endl; 
	}
	return 0;
}

6.阶乘之和

时间限制:3000 ms  |  内存限制:65535 KB
难度:3
描述
给你一个非负数整数n,判断n是不是一些数(这些数不允许重复使用,且为正数)的阶乘之和,如9=1!+2!+3!,如果是,则输出Yes,否则输出No;

输入
第一行有一个整数0<m<100,表示有m组测试数据;
每组测试数据有一个正整数n<1000000;
输出
如果符合条件,输出Yes,否则输出No;
样例输入
2
9
10
样例输出
 Yes
No

提示:首先把阶乘小于1000000的数列出来,然后怎样去判断一个数是不是这些数的阶乘之和呢?

题目中有一个很重要的信息叫做,没个数只能用一次。这就告诉我们只需单向遍历数组就够了。一个数要是这些数的和,那肯定是比他小的数累加得来的,而阶乘数之间存在着倍数关系,所以每次只用查找比n小的最大阶乘数。

 
#include <cstdio>
using namespace std;
int s[9] = {1,2,6,24,120,720,5040,40320,362880};
 
int main()
{
    int t, n;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        for(int i=8; i>=0; i--)
        {
            if(n>=s[i])
            {
                n -= s[i];
                if(n == 0) {printf("Yes\n");break;}
            }
        }
        if(n != 0) printf("No\n");
    }
    return 0;
}

上一题转自https://blog.csdn.net/greenhandcgl/article/details/44339129 

其他贪心法题目见https://blog.csdn.net/liujiuxiaoshitou/article/details/69728714

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值