【C++】贪心算法入门

简介

贪心算法又叫贪婪算法,对于局部最优解就是全局最优解的问题,可以按照局部最优解的决策层层递进获得全局最优解。

例题

01背包

题目描述

n n n个物品,物品i的体积为 v [ i ] v[i] v[i]。现有一个容量为 C C C背包,

请计算该背包能装载物品的最大数量。

注意:每个物品要么全装入,要么不装入,不能只装一部分。
输入格式
1 1 1行是 n n n C C C

接下来一行有 n n n个整数,第 i i i个数表示物品的体积 v [ i ] v[i] v[i]

输出格式

输出一个整数,表示能装入的最大物品数量。

样例输入

6 10
1 5 3 2 4 6

样例输出

4

题解
本题是01背包的变形,01背包原题求最大装入价值,本题求的是能装入的最大数量,每次在剩余物品种选取体积最小的物品即可。

#include <iostream>
#include <algorithm>
using namespace std;
int main(){
	int n,c,v[50001],sum=0,cnt=0;
	cin>>n>>c;
	for(int i=0;i<n;i++){
		cin>>v[i];//输入n个物品的体积
	}
	sort(v,v+n);//体积排序
	for(int i=0;i<n;i++){
		if(sum+v[i]<=c){//能装下
			sum+=v[i];
			cnt++;
		}else{
			break;
		}
	}
	cout<<cnt;//输出装的个数
	return 0;
}

部分背包

题目描述
n n n个物品,物品 i i i的体积为 v [ i ] v[i] v[i],价值为 p [ i ] p[i] p[i]。现有容量为 C C C的背包,最多能装载 C C C的体积,

请计算怎样装入才能使背包中装载的的物品价值最高。
注意:物品可部分装载,如果商品 i i i只装入 x x x部分,则价值为: ( p [ i ] ∗ x / v [ i ] ) (p[i]∗x/v[i]) (p[i]x/v[i])

输入格式
1 1 1行是$n 和 C;

2 2 2行有 n n n个整数,表示物品的体积 v [ i ] v[i] v[i]

3 3 3行有 n n n个整数,表示物品的价值 p [ i ] p[i] p[i]

输出格式
输出一个小数,表示装入物品的最大价值,保留 2 2 2位小数。

样例输入

6 10
1 5 3 2 4 6
1 6 2 3 5 5

样例输出

12.80

数据范围与提示
0 < n ≤ 50000 0 < C , v [ i ] , p [ i ] ≤ 1 0 9 0<n\le 50000\\ 0<C,v[i],p[i]\le 10^9 0<n500000<C,v[i],p[i]109

题解
由于可以部分装入,每次选择性价比最高的背包即可取得最有解,性价比可以以单位体积的价值表示。

#include <iostream>
#include <algorithm>
using namespace std;
typedef struct{
	int v,p;//体积和价值
	float vp;//性价比
}item;
bool cmp(item a,item b){
	return a.vp>b.vp;//性价比排序
}
int main(){
	int n,c;
	item pack[50000];
	cin>>n>>c;
	for(int i=0;i<n;i++){
		cin>>pack[i].v;//输入物品体积
	}
	for(int i=0;i<n;i++){
		cin>>pack[i].p;//输入物品价值
		pack[i].vp=pack[i].p/float(pack[i].v);
		//计算性价比
	}
	sort(pack,pack+n,cmp);//性价比降序
	float ans=0;
	for(int i=0;i<n;i++){
		if(c>0){//没装满
			if(c>=pack[i].v){//完全装入
				ans+=pack[i].p;
				c-=pack[i].v;
			}else{
				ans+=pack[i].vp*c;//部分装入
				c=0;
				break;
			}
		}
	}
	printf("%.2f",ans);
	return 0;
}

购买贺年卡

题目描述

新年快到了,笑笑打算给他的好朋友们发贺年卡,而且他已经选好了自己要购买的贺卡的样式。俗话说得好,货比三家,笑笑来到商店,看了各个商铺这种贺卡的价钱。不仅如此,笑笑还记住了每个商铺的存货量。
已知笑笑打算购买 m m m张贺卡,问他最少花多少钱

输入格式

第一行两个整数 m m m n n n。其中 m m m表示要购买的贺卡的数量, n n n 表示商铺的个数。

以下 n n n行,每行两个整数,分别表示该商铺这种贺卡的单价和存货量。

输出格式

仅一个数,表示笑笑花的最少钱数。

样例输入

10 4
4 3
6 2
8 10
3 6

样例输出

36

样例解释

先将最后一家买空( 3 3 3元* 6 6 6 = = = 18 18 18元)

再将第一家买空( 4 4 4元* 3 3 3 = = = 12 12 12元)

再从第二家买一张( 6 6 6元* 1 1 1 = = = 6 6 6元)

刚好十张,总价格 36 36 36

保证结果在长整型以内且总存货量不少于 m m m,保证有且只有一个最优解
0 < m , n < = 1000 0<m,n<=1000 0<m,n<=1000

题解

按照贺卡单价排序即可,每次都购买最便宜的贺卡。

#include <iostream>
#include <algorithm>
using namespace std;
struct node{
	int a, b;//贺卡单价和存货量
}t[1005];
int cmp (node a, node b){
	return a.a<b.a;//单价排序
}
int main() {
	int n,m;
	int sum=0;
	cin>>n>>m;
	for(int i=0;i<m;i++){
		cin>>t[i].a>>t[i].b;
	}
	sort(t,t+m,cmp);
	for (int i=0;i<m;i++){
		if(n>=t[i].b){//数量不够
			n=n-t[i].b;//需要购买数量减少
			sum+=t[i].a*t[i].b;//全买
		}else{
			sum+=n*t[i].a;//只买n张
			break;
		}
	}
	cout<<sum<<endl;
	return 0;
}

硬币问题

题目描述

1 、 5 、 10 、 50 、 100 、 500 1、5、10、50、100、500 151050100500元的硬币各 c 0 、 c 1 、 c 2 、 c 3 、 c 4 、 c 5 c0、c1、c2、c3、c4、c5 c0c1c2c3c4c5个,现在要用这些硬币来支付 s u m sum sum元,最少要多少枚硬币?

假定本题至少存在一种方案

输入格式

一行六个整数,分别表示 s u m , c 0 , c 1 , c 2 , c 3 , c 4 , c 5 sum, c0, c1, c2, c3, c4, c5 sum,c0c1,c2,c3,c4,c5.

输出格式

最少需要多少个硬币。

样例输入

620 3 2 1 3 0 2 

样例输出

6

数据范围与提示
s u m ≤ 10000 sum \le 10000 sum10000
零钱的张数均小于 1000 1000 1000

题解

每次都挑选面额最大的硬币,就能得到最少硬币枚数的方案。

#include <iostream>
using namespace std;
int main(){
	int idx=5;
	int sum,ans=0;//表示sum元和结果的枚数
	int v[6]={1,5,10,50,100,500};//表示面值
	int c[6];//各面值的个数  
	cin>>sum;
	for(int i=0;i<6;i++) cin>>c[i];
	while(sum){
		while(v[idx]<=sum&&c[idx]){//能选取 
			sum-=v[idx];//需要的面额减小 
			c[idx]--;//本面额数量减少 
			ans++;//答案枚数增加 
		}
		idx--;//不能选了,选面额更小的那一个 
	}
	cout<<ans;
	return 0;
}

删数问题

题目描述

输入一个高精度的正整数 n n n,去掉其中任意 s s s个数字后剩下的数字按原左右次序组成一个新的正整数。

编程对给定的 n n n s s s,寻找一种方案使得剩下的数字组成的新数最小。

输出新的正整数。( n n n不超过 240 240 240位)

输入格式

一行两个整数,输入 n n n s s s

输出格式

最后剩下的最小数。

输入样例

175438
4

输出样例

13

题解

为了提高精度,可以用字符串存取数字,由于删除后可能数字含有多个前导0,需要删除。删数问题符合贪心算法,每次删掉后得到的最小数字就是全局最优解的子步骤。在这里定义存在字符串 s s s,逆序位为满足 s [ i ] > s [ i + 1 ] s[i]>s[i+1] s[i]>s[i+1]的下标位置 i i i,每轮找到数字逆序位的最高位删除即可,如数字 12320 12320 12320,其中的逆序位为 s [ 2 ] = 3 , s [ 3 ] = 2 s[2]=3,s[3]=2 s[2]=3,s[3]=2,最高位是 s [ 2 ] s[2] s[2],删掉后得到的最小数字是 1220 1220 1220,再删除一次是 120 120 120。如果不存在逆序位,说明数字位数递增,应当删除个位。

#include <iostream>
#include <string>
using namespace std;
string solve(string s,int n){//剩余s串中删去n个
	if(n==0){
		while(s.size()>1&&s[0]=='0') s.erase(0,1);//删除前导多余0
		return s;
	}else{
		int i;
		for(i=0;i<(int)s.size()-1;i++){//找到逆序的下标i
			if(s[i]>s[i+1]) break;
		}
		s.erase(i,1);//删除第i位
		return solve(s,n-1);//递归删除
	}
}
int main(){
	string s;
    int n;
	cin>>s>>n;
	cout<<solve(s,n);
	return 0;
}

拦截导弹问题

题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于 30000 30000 30000的正整数),计算要拦截所有导弹最小需要配备多少套这种导弹拦截系统。

输入格式
一行,为导弹依次飞来的高度。(个数$\le$10000)

输出格式

要拦截所有导弹最少要配备的系统数。

输入样例

389 207 155 300 299 170 158 65

输出样例

2

题解

题意可以转换为求数组中至少有多少个递减序列。用标记法模拟即可,每次取出一个递减序列,看看至少需要取几次才能取出所有元素。

#include <iostream>
using namespace std;
int main(){
	int times=0,num[10000],len=0;
	//次数,导弹高度,导弹数量 
	while(cin>>num[len]) len++;//输入导弹高度 
	for(int i=0;i<len;i++){//扫描导弹 
		if(num[i]==-1) continue;//-1表示已被拦截 
		int pre=num[i],pos=i+1;
		num[i]=-1;//标记被拦截 
		times++;//需要拦截一次 
		//pre表示上一枚被击中高度,pos表示扫描到第几枚导弹 
		while(pos<len){//小于导弹个数 
			if(num[pos]!=-1&&num[pos]<=pre){
				//如果没有被拦截且高度小于等于上一枚 
				pre=num[pos];//更新上一枚被拦截高度 
				num[pos]=-1;//记录被拦截 
			}
			pos++;//扫描下一枚导弹 
		}
	}
	cout<<times;//输出拦截系统数 
	return 0;
}

排队接水

题目描述

n n n个人在一个水龙头前排队接水,假如每个人接水的时间为 T i T_i Ti

请编程找出这 n n n个人排队的一种顺序,使得 n n n个人的平均等待时间最小。

输入格式

共两行,第一行为 n ( 1 ≤ n ≤ 1000 ) n(1\le n\le 1000) n(1n1000)

第二行分别表示第 1 1 1个人到第 n n n个人每人的接水时间$T_1,T_2,\dots ,T_n,每个数据之间有 1 1 1个空格。

输出格式

有两行,第一行为一种排队顺序,即 1 1 1 n n n的一种排列;

第二行为这种排列方案下的平均等待时间(输出结果精确到小数点后两位)。

输入样例

10							
56 12 1 99 1000 234 33 55 99 812

输出样例

3 2 7 8 1 4 9 6 10 5
291.90

数据范围与提示
t i ≤ 1 0 6 t_i\le 10^6 ti106,不保证 t i t_i ti不重复

t i t_i ti重复时,按照输入顺序即可(sort是可以的)

题解

要想总的平均等待时间最少,则每次需选择接水时间最短的人来接水,可以先按接水时间升序排序,然后计算接水时间$,由于需要输出节水顺序,重复时按照输入顺序接水,需要设计结构体存取输入顺序,以接水时间、输入顺序进行多关键字排序。

#include <iostream>
#include <algorithm>
using namespace std;
typedef struct{
	int seq,T;//次序和时间 
}que;
bool cmp(que a,que b){//排序逻辑
	if(a.seq!=b.seq) return a.T<b.T;
	else return a.seq<b.seq; 
}
int main(){
	int n;
	que num[1000];
	cin>>n;
	for(int i=0;i<n;i++){
		num[i].seq=i+1;//次序 
		cin>>num[i].T;//时间 
	}
	sort(num,num+n,cmp);//排序 
	int ans=0;
	for(int i=1;i<=n;i++){
		cout<<num[i-1].seq<<' ';//输出次序 
		ans+=num[i-1].T*(n-i);//计算时间,因式分解后是这样 
	}
	printf("\n%.2f",float(ans)/n);//输出平均时间 
	return 0;
}

均分纸牌

题目描述

N N N堆纸牌,编号分别为 1 , 2 , … , N 1,2,…, N 12,N。每堆上有若干张,但纸牌总数必为 N N N的倍数。可以在任一堆上取若于张纸牌,然后移动。

移牌规则为:在编号为 1 1 1堆上取的纸牌,只能移到编号为 2 2 2的堆上;在编号为 N N N的堆上取的纸牌,只能移到编号为 N − 1 N-1 N1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。

现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

例如 N = 4 N=4 N=4 4 4 4堆纸牌数分别为:① 9 9 9,② 8 8 8,③ 17 17 17,④ 6 6 6移动 3 3 3次可达到目的:从③取 4 4 4张牌放到④ ( 981310 ) (9 8 13 10) (981310)->从③取 3 3 3张牌放到② ( 9111010 ) (9 11 10 10) (9111010)->从②取 1 1 1张牌放到① ( 10101010 ) (10 10 10 10) (10101010)

输入格式

N N N N N N堆纸牌, 1 ≤ N ≤ 100 1\le N\le 100 1N100)$ A_1,A_2,… ,A_n ( ( N$堆纸牌,每堆纸牌初始数, 1 ≤ A i ≤ 10000 1\le Ai\le 10000 1Ai10000

输出格式

所有堆均达到相等时的最少移动次数。

样例输入

4
9 8 17 6

样例输出

3

题解

先求出平均每堆纸牌数,然后每堆纸牌数与平均每堆纸牌数相减差值,模拟拿纸牌如何使每堆的差值都是 0 0 0。为保证移动次数最小,可以假设第 i i i堆只从第 i + 1 i+1 i+1堆拿纸牌(如果是第 i + 1 i+1 i+1堆从第 i i i堆拿纸牌,则等价于第 i i i堆从第 i + 1 i+1 i+1堆拿了负数张纸牌),计算最少移动次数即可。

#include <iostream>
using namespace std;
int main(){
	int num[100],ave=0,len;
	cin>>len;//输入堆数
	for(int i=0;i<len;i++){
		cin>>num[i];//输入纸牌数 
		ave+=num[i];//记录和 
	}
	ave/=len;//平均数
	for(int i=0;i<len;i++){
		num[i]-=ave;//求差值 
	}
	int sum=0;//移动数
	for(int i=0;i<len-1;i++){
		if(num[i]!=0){
			sum++;//次数+1 
			num[i+1]+=num[i];//更新移动后后一堆纸牌数 
		}
	}
	cout<<sum;
	return 0;
}

版权声明

  • 本文档归cout0所有,仅供学习使用,未经允许,不得转载。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cout0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值