简介
贪心算法又叫贪婪算法,对于局部最优解就是全局最优解的问题,可以按照局部最优解的决策层层递进获得全局最优解。
例题
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<n≤500000<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 1、5、10、50、100、500元的硬币各 c 0 、 c 1 、 c 2 、 c 3 、 c 4 、 c 5 c0、c1、c2、c3、c4、c5 c0、c1、c2、c3、c4、c5个,现在要用这些硬币来支付 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,c0,c1,c2,c3,c4,c5.
输出格式
最少需要多少个硬币。
样例输入
620 3 2 1 3 0 2
样例输出
6
数据范围与提示
s
u
m
≤
10000
sum \le 10000
sum≤10000
零钱的张数均小于
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(1≤n≤1000);
第二行分别表示第 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
ti≤106,不保证
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 1,2,…,N。每堆上有若干张,但纸牌总数必为 N N N的倍数。可以在任一堆上取若于张纸牌,然后移动。
移牌规则为:在编号为 1 1 1堆上取的纸牌,只能移到编号为 2 2 2的堆上;在编号为 N N N的堆上取的纸牌,只能移到编号为 N − 1 N-1 N−1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如 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 1≤N≤100)$ A_1,A_2,… ,A_n ( ( (N$堆纸牌,每堆纸牌初始数, 1 ≤ A i ≤ 10000 1\le Ai\le 10000 1≤Ai≤10000)
输出格式
所有堆均达到相等时的最少移动次数。
样例输入
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所有,仅供学习使用,未经允许,不得转载。