贪心算法

贪心算法

1、贪心算法基础概念
所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪婪算法可以寻找局部最优解,并尝试与这种方式获得全局最优解。
  贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。(即每一步行动都是按照某种指标选取最优的操作来进行,该指标只看眼前,并不考虑以后可能造成的影响。)
  所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。 而贪心算法需要证明其正确性,比如在圣诞老人礼物这题中,若糖果只能整箱拿,则贪心算法就是错误的;例如:三个箱子(8,6)(5,5)(5,5),雪橇总容量10,那么选价值比最大的第一个,其并不是最优解,可以拿后两个;背包问题用这种方法实现得到的结果并不是最优解
能用贪心解决的问题,也可以用动态规划解决。

2、 贪心算法的基本思路:

  • 建立数学模型来描述问题。
  • 把求解的问题分成若干个子问题。
  • 对每一子问题求解,得到子问题的局部最优解。
  • 把子问题的解局部最优解合成原来解问题的一个解。

    • 贪心策略适用的前提是:局部最优策略能导致产生全局最优解。

    3、圣诞老人的礼物
    描述
    圣诞节来临了,在城市A中圣诞老人准备分发糖果,现在有多箱不同的糖果,每箱糖果有自己的价值和重量,每箱糖果都可以拆分成任意散装组合带走。圣诞老人的驯鹿最多只能承受一定重量的糖果,请问圣诞老人最多能带走多大价值的糖果。
    输入
    第一行由两个部分组成,分别为糖果箱数正整数n(1 <= n <= 100),驯鹿能承受的最大重量正整数w(0 < w < 10000),两个数用空格隔开。其余n行每行对应一箱糖果,由两部分组成,分别为一箱糖果的价值正整数v和重量正整数w,中间用空格隔开。
    输出
    输出圣诞老人能带走的糖果的最大总价值,保留1位小数。输出为一行,以换行符结束。
    样例输入
    4 15
    100 4
    412 8
    266 7
    591 2
    样例输出
    1193.0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    #include<iostream>
    #include<algorithm>
    #include <iomanip>         //用setprecision这个的时候需要添加这个头文件,其中precision是精度的意思
    using namespace std;
    const double eps=1e-6;		//表示1*10的-6次方,用来表示精度
    struct Candy{
    	int v;
    	int w;
    	bool operator<(const Candy &c){ 		//重载了 < 运算符
    		return double(v)/w-double(c.v)/c.w>eps;   //这一部分的意思是啥
    	}
    }candies[110];
    int main(){
    	int n,w;
    	cin>>n>>w;
    	for(int i=0;i<n;++i){
    		cin>>candies[i].v>>candies[i].w;
    	}
    	sort(candies,candies+n);		//所以时间复杂度为O(NLogN)
    	int totalw=0;
    	double totalv=0;
    	for(int i=0;i<n;++i){
    		if(totalw+candies[i].w<w){
    			totalw+=candies[i].w;
    			totalv+=candies[i].v;
    			
    		}
    		else{
    			totalv+=candies[i].v*double(w-totalw)/candies[i].w;
    			break;
    		}
    	}
    	cout<<setprecision(1)<<fixed<<totalv;
    	return 0;
    }
    

4、Go fishing(百练 1042 _贪心+枚举)

upload successful

upload successful

思路:
枚举停止在的湖编号,用总时间减去到该湖的走的路程的时间即钓鱼的纯时间,在这段时间内,每次都找剩余鱼量最多的湖,而且当鱼量出现负数时,设置为0
注意,假如所有湖剩余都为0,则跳出循环,将剩余时间加到第一个湖上
解题:
输入初始鱼量、每片减少的鱼量以及路程时间
对每个湖假设停止并遍历,计算纯钓鱼的时间,在这段时间内,选择剩余鱼量最多的湖,该湖时间加一,并且更新鱼量,记得不能为负数,然后遍历湖假如此时湖剩余鱼量全为0,则跳出循环,并且记得将剩余时间全都给第一个湖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <string>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
int main()
{
    int n,h;  //n指n个湖,h指钓鱼所用的总时间
    cin>>n;  
    while(n!=0){
        cin>>h;
        int result[n],tmp=-1;
        int fi[n],di[n],ti[n-1];
        //fi指没每个鱼塘初始鱼的数量,di指每5分钟后鱼塘中鱼减少的速率,ti为从一个鱼塘走到另一个鱼塘所用的时间
        for(int i=0;i<n;i++)cin>>fi[i];
        for(int i=0;i<n;i++)cin>>di[i];
        for(int i=0;i<n-1;i++)cin>>ti[i];
        
        int ft[n];
        for(int i=0;i<n;i++)ft[i]=fi[i];
        
        for(int k=0;k<n;k++){
            int result2[n];
            for(int i=0;i<n;i++){
                result2[i]=0;
            }
            int ht=h*12;
            int test=0;
            for(int j=0;j<k;j++)ht-=ti[j];
            for(int i=0;i<n;i++)ft[i]=fi[i];
            int emp=0;
            while(ht--&&emp<=k){//计算时间
                int a=0;
                for(int j=0;j<=k;j++){//计算最大的剩余的鱼
                    if(ft[a]<ft[j])a=j;
                }
                test+=ft[a];
                result2[a]++;
                ft[a]-=di[a];
                if(ft[a]<=0)ft[a]=0;
                for(int j=emp; j<=k; j++)//如果不进行统计就会超时
                {
                    if(ft[j]==0)
                        ++emp;//统计有几个湖已经没有鱼
                    else
                        break;
                }
 
            }
            if(test>tmp){
                for(int i=0;i<n;i++){
                    result[i]=result2[i];
                }
                if(ht>0)//如果还有时间就加到第一个湖上
                    result[0]+=ht;
                tmp=test;
            }
            
        }
        for(int i=0;i<n;i++){
            cout<<result[i]*5;
            if(i!=n-1)cout<<", ";
        }
        cout<<endl;
        cout<<"Number of fish expected: "<<tmp<<endl;
        cout<<endl;
        cin>>n;
    }
}

5、【题目描述】
  有n堆纸牌,编号分别为 1,2,…, n。每堆上有若干张,但纸牌总数必为n的倍数。可以在任一堆上取若干张纸牌,然后移动。
移牌规则为:在编号为1的堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 n 的堆上取的纸牌,只能移到编号为n-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
  现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如 n=4,4堆纸牌数分别为: ① 9 ② 8 ③ 17 ④ 6
移动3次可达到目的:
  从 ③ 取4张牌放到④(9 8 13 10)->从③取3张牌放到 ②(9 11 10 10)-> 从②取1张牌放到①(10 10 10 10)。
【输入】
  n(n 堆纸牌,1 ≤ n ≤ 100)
  a1 a2 … an (n 堆纸牌,每堆纸牌初始数,l≤ ai ≤10000)。
【输出】
 所有堆均达到相等时的最少移动次数。
【输入样例】
 4
 9 8 17 6
【输出样例】
 3
思路:
求平均数,然后每个数减去平均数,然后不管是正数还是负数,都向下一个移动,最后会都等于0。至于这题是贪心,应该是每一步骤都是变为0,这种求解吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
#include<cstdio>
using namespace std;
int a[10000];
int main(){
	int n,sum=0,i,j,step=0;
	cin>>n;j=n;i=1;
	for(int i=1;i<=n;++i)
	{
		cin>>a[i];
		sum+=a[i];
	}
	for(int i=1;i<=n;++i)
	{
		a[i]-=sum/n;     //记录比平均数多或少的那部分值就好了
	}
	while(a[i]==0&&i<n)++i;
	while(a[j]==0&&j>1)--j;
	while(i<j)
	{
		a[i+1]+=a[i];   //相当于把当前那个不是平均数的数用后面那个数来补上,理想这样是最优的
		a[i]=0;  // 把当前这个归位,不归也可以哈
		step++;
		i++;
		while(a[i]==0&&i<j)++i;   //优化,只处理堆中不是平均数的就好了
	}
	cout<<step;
}

6、贪心算法之钱币找零问题
  假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0, c1, c2, c3, c4, c5, c6张。现在要用这些钱来支付K元,至少要用多少张纸币?用贪心算法的思想,很显然,每一步尽可能用面值大的纸币即可。在日常生活中我们自然而然也是这么做的。在程序中已经事先将Value按照从小到大的顺序排好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream>
#include<algorithm>
using namespace std;
const int N=7; 
int Count[N]={3,0,2,1,0,3,5};
int Value[N]={1,2,5,10,20,50,100};
  
int solve(int money) 
{
	int num=0;
	for(int i=N-1;i>=0;i--) 
	{
		int c=min(money/Value[i],Count[i]); 
        //这样的话就是可以取到的面值较大的张数的最小值,因为money/Value[i]的值可以是2,不只是1
		money=money-c*Value[i];
		num+=c;
	}
	if(money>0) num=-1;
	return num;
}
 
int main() 
{
	int money;
	cin>>money;
	int res=solve(money);
	if(res!=-1) cout<<res<<endl;
	else cout<<"NO"<<endl;
}

7、放置雷达(百练1328)

题目描述:
假定海岸线是一条无限延伸的直线,陆地在海岸线的一边,大海在另一侧。海中有许多岛屿,每一个小岛我们可以认为是一个点。现在要在海岸线上安装雷达,雷达的覆盖范围是d,也就是说大海中一个小岛能被安装的雷达覆盖,那么它们之间的距离最大为d。
我们使用平面直角坐标系,定义海岸线是x轴,大海在x轴上方,陆地在下方。给你海中每一个岛屿的坐标位置(x,y)和要安装的雷达所覆盖的范围d,你的任务是写一个程序计算出至少安装多少个雷达能将所有的岛屿覆盖。
输入描述:
第一行两个整数n(1≤n≤100000)和d,分别表示海中岛屿的数目和雷达覆盖的范围半径d。
接下来n行,每行两个整数,表示每个岛屿的坐标位置(x,y)。
输出描述:
一行一个整数,即能将所有岛屿全部覆盖至少安装的雷达个数,如果无解则输出“-1”。
样例输入
3 2
1 2
-3 1
2 1

1 2
0 2

0 0
样例输出
Case 1: 2
Case 2: 1

贪心算法:
如果求在x轴上有哪些点可以覆盖雷达,然后枚举,这样做很麻烦,所以转化到岛屿能被扫描到的区间的比较问题上。
1 ) 将所有区间按照起点从小到大排序,并编号0 - (n-1)
2 ) 依次考察每个区间的起点,看要不要在那里放雷达。开始,所有区间都没被覆盖,所以目前编号最小的未被覆盖的区间的编号 firstNoConverd =0
3 ) 考察一个区间 i 的起点 xi的时候,要看从 firstNoCovered 到区间i-1 中是否存在某个区间 c ,没有被 xi 覆盖。如果没有,则先不急于在xi放雷达,接着往下看。如果有,那么 c 的终点肯定在xi的左边,因此不可能用同一个雷达覆盖 c 和i。即能覆盖c的点,已经不可能覆盖i和i后面的区间了。此时,为了覆盖c,必须放一个雷达了,放在区间 i-1 的起点即可覆盖所有从firstNoCovered到 i-1的区间。因为当初考察 i-1的起点 z时候,并没有发现 z 漏覆盖了从 firstNoCovered 到 i-2 之间的任何一个区间
4 ) 放完雷达后,将 firstNoCovered改为i,再做下去。

upload successful

upload successful
证明:

upload successful

upload successful

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <cmath>#include <algorithm>
#define NUM 1001
using namespace std;
    //n代表小岛数,N代表小岛在island_x轴上的所有区间个数
 
struct QU_jian
{
    double l;//左边点
    double r;//右边点
    bool operator<(const QU_jian& t)const{
        return this->l<t.l;
    }
}N[NUM];
 
 
 
int main()
{
    int n,d;
 
    double island_x,island_y;//每一个小岛的坐标
    int k = 1;//案例数
 
    while(cin>>n>>d&&n&&d){
 
        int radar_min_num = 1;
        for (int i = 0;i<n;i++){
            cin>>island_x>>island_y;
            if(island_y<=d && d>0 && island_y>=0){
                N[i].r=island_x+(double)sqrt(d*d - island_y*island_y);
                N[i].l=island_x-(double)sqrt(d*d - island_y*island_y);
            }
            else{
                radar_min_num = -1;//读错半径和island_y的坐标继续读
            }
        }
            //用几何关系找到每一个小岛需要固定的半径的雷达在island_x轴上的最大和最小值范围
 
        sort(N, N+n);
            //对左端的数据从小到大排序
 
 
        double temp = N[0].r;
        for( int i = 1;(i<n && radar_min_num != -1);i++){
            if(temp<N[i].l){
                temp = N[i].r;
                radar_min_num++;
            }
            else{
                if(N[i].r<temp)
                    temp = N[i].r;
            }
        }
            //利用贪心算法找到最优
        cout <<"Case "<<k++<<": "<<radar_min_num<<endl;//打印结果
    }
    return 0;
}

https://blog.csdn.net/NNNNNNNNNNNNY/article/details/51721722
????这种方法没看懂,二分与贪心

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值