【C++】贪心算法入门

308 阅读2分钟

简介

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

例题

01背包

题目描述

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

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

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

接下来一行有nn个整数,第ii个数表示物品的体积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;
}

部分背包

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

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

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

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

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

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

样例输入

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

样例输出

12.80

数据范围与提示

0<n500000<C,v[i],p[i]1090<n\le 50000\\ 0<C,v[i],p[i]\le 10^9

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

#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;
}

购买贺年卡

题目描述

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

输入格式

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

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

输出格式

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

样例输入

10 4
4 3
6 2
8 10
3 6

样例输出

36

样例解释

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

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

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

刚好十张,总价格3636

保证结果在长整型以内且总存货量不少于mm,保证有且只有一个最优解 0<m,n<=10000<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;
}

硬币问题

题目描述

1510501005001、5、10、50、100、500元的硬币各c0c1c2c3c4c5c0、c1、c2、c3、c4、c5个,现在要用这些硬币来支付sumsum元,最少要多少枚硬币?

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

输入格式

一行六个整数,分别表示sum,c0c1,c2,c3,c4,c5sum, c0, c1, c2, c3, c4, c5.

输出格式

最少需要多少个硬币。

样例输入

620 3 2 1 3 0 2 

样例输出

6

数据范围与提示

sum10000sum \le 10000

零钱的张数均小于10001000

题解

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

#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;
}

删数问题

题目描述

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

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

输出新的正整数。(nn不超过240240位)

输入格式

一行两个整数,输入nnss

输出格式

最后剩下的最小数。

输入样例

175438
4

输出样例

13

题解

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

#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;
}

拦截导弹问题

题目描述

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

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

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

输出格式

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

输入样例

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;
}

排队接水

题目描述

nn个人在一个水龙头前排队接水,假如每个人接水的时间为TiT_i

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

输入格式

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

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

输出格式

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

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

输入样例

10							
56 12 1 99 1000 234 33 55 99 812

输出样例

3 2 7 8 1 4 9 6 10 5
291.90

数据范围与提示 ti106t_i\le 10^6,不保证tit_i不重复

tit_i重复时,按照输入顺序即可(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;
}

均分纸牌

题目描述

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

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

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

例如N=4N=444堆纸牌数分别为:①99,②88,③1717,④66移动33次可达到目的:从③取44张牌放到④(981310)(9 8 13 10)->从③取33张牌放到②(9111010)(9 11 10 10)->从②取11张牌放到①(10101010)(10 10 10 10)

输入格式

NNNN堆纸牌,1N1001\le N\le 100A1A2AnA_1,A_2,… ,A_nNN堆纸牌,每堆纸牌初始数,1Ai100001\le Ai\le 10000

输出格式

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

样例输入

4
9 8 17 6

样例输出

3

题解

先求出平均每堆纸牌数,然后每堆纸牌数与平均每堆纸牌数相减差值,模拟拿纸牌如何使每堆的差值都是00。为保证移动次数最小,可以假设第ii堆只从第i+1i+1堆拿纸牌(如果是第i+1i+1堆从第ii堆拿纸牌,则等价于第ii堆从第i+1i+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所有,仅供学习使用,未经允许,不得转载。