简介
贪心算法又叫贪婪算法,对于局部最优解就是全局最优解的问题,可以按照局部最优解的决策层层递进获得全局最优解。
例题
01背包
题目描述
有个物品,物品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 和 C;
第行有个整数,表示物品的体积;
第行有个整数,表示物品的价值。
输出格式 输出一个小数,表示装入物品的最大价值,保留位小数。
样例输入
6 10
1 5 3 2 4 6
1 6 2 3 5 5
样例输出
12.80
数据范围与提示
题解 由于可以部分装入,每次选择性价比最高的背包即可取得最有解,性价比可以以单位体积的价值表示。
#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;
}
购买贺年卡
题目描述
新年快到了,笑笑打算给他的好朋友们发贺年卡,而且他已经选好了自己要购买的贺卡的样式。俗话说得好,货比三家,笑笑来到商店,看了各个商铺这种贺卡的价钱。不仅如此,笑笑还记住了每个商铺的存货量。 已知笑笑打算购买张贺卡,问他最少花多少钱
输入格式
第一行两个整数和。其中表示要购买的贺卡的数量, 表示商铺的个数。
以下行,每行两个整数,分别表示该商铺这种贺卡的单价和存货量。
输出格式
仅一个数,表示笑笑花的最少钱数。
样例输入
10 4
4 3
6 2
8 10
3 6
样例输出
36
样例解释
先将最后一家买空(元*6$$=$$18元)
再将第一家买空(元*3$$=$$12元)
再从第二家买一张(元*1$$=$$6元)
刚好十张,总价格元
保证结果在长整型以内且总存货量不少于,保证有且只有一个最优解
题解
按照贺卡单价排序即可,每次都购买最便宜的贺卡。
#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;
}
硬币问题
题目描述
有元的硬币各个,现在要用这些硬币来支付元,最少要多少枚硬币?
假定本题至少存在一种方案
输入格式
一行六个整数,分别表示.
输出格式
最少需要多少个硬币。
样例输入
620 3 2 1 3 0 2
样例输出
6
数据范围与提示
零钱的张数均小于
题解
每次都挑选面额最大的硬币,就能得到最少硬币枚数的方案。
#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;
}
删数问题
题目描述
输入一个高精度的正整数,去掉其中任意个数字后剩下的数字按原左右次序组成一个新的正整数。
编程对给定的和,寻找一种方案使得剩下的数字组成的新数最小。
输出新的正整数。(不超过位)
输入格式
一行两个整数,输入和 。
输出格式
最后剩下的最小数。
输入样例
175438
4
输出样例
13
题解
为了提高精度,可以用字符串存取数字,由于删除后可能数字含有多个前导0,需要删除。删数问题符合贪心算法,每次删掉后得到的最小数字就是全局最优解的子步骤。在这里定义存在字符串,逆序位为满足的下标位置,每轮找到数字逆序位的最高位删除即可,如数字,其中的逆序位为,最高位是,删掉后得到的最小数字是,再删除一次是。如果不存在逆序位,说明数字位数递增,应当删除个位。
#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;
}
拦截导弹问题
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于的正整数),计算要拦截所有导弹最小需要配备多少套这种导弹拦截系统。
输入格式 一行,为导弹依次飞来的高度。(个数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;
}
排队接水
题目描述
有个人在一个水龙头前排队接水,假如每个人接水的时间为,
请编程找出这个人排队的一种顺序,使得个人的平均等待时间最小。
输入格式
共两行,第一行为;
第二行分别表示第个人到第个人每人的接水时间1$个空格。
输出格式
有两行,第一行为一种排队顺序,即到的一种排列;
第二行为这种排列方案下的平均等待时间(输出结果精确到小数点后两位)。
输入样例
10
56 12 1 99 1000 234 33 55 99 812
输出样例
3 2 7 8 1 4 9 6 10 5
291.90
数据范围与提示 ,不保证不重复
当重复时,按照输入顺序即可(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;
}
均分纸牌
题目描述
有堆纸牌,编号分别为。每堆上有若干张,但纸牌总数必为的倍数。可以在任一堆上取若于张纸牌,然后移动。
移牌规则为:在编号为堆上取的纸牌,只能移到编号为的堆上;在编号为的堆上取的纸牌,只能移到编号为的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如,堆纸牌数分别为:①,②,③,④移动次可达到目的:从③取张牌放到④->从③取张牌放到②->从②取张牌放到①。
输入格式
(堆纸牌,)(堆纸牌,每堆纸牌初始数,)
输出格式
所有堆均达到相等时的最少移动次数。
样例输入
4
9 8 17 6
样例输出
3
题解
先求出平均每堆纸牌数,然后每堆纸牌数与平均每堆纸牌数相减差值,模拟拿纸牌如何使每堆的差值都是。为保证移动次数最小,可以假设第堆只从第堆拿纸牌(如果是第堆从第堆拿纸牌,则等价于第堆从第堆拿了负数张纸牌),计算最少移动次数即可。
#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所有,仅供学习使用,未经允许,不得转载。