基础算法:贪心

贪心算法的定义与性质:

  • 贪心算法是通过做出一系列 选择 来求出问题的最优解。在每一个决策点,它作出在 当前看来最好 的选择。

    • (也就是说贪心算法并 不从整体最优上加以考虑 ,它所作出的选择只是在某种意义上的 局部最优选择 。大概也是贪心算法名字的由来)

    • (贪心算法进行决策时, 可能依赖于以往所做出的选择,但决不依赖于将来所做的选择,或者子问题的解 。)

  • 贪心算法必须满足 最优子结构 。与动态规划不同,贪心的最优子结构,必须满足 选择后只留下一个子问题

    • (从最优子结构的角度说:相当于特殊的动态规划。动态规划的选择后往往会产生多个子问题。)

    • (正是由于这种差异,动态规划算法通常以自底向上的方式解各子问题,而 贪心算法则通过以自项向下的方式 进行,以 迭代 的方式做出相继的贪心选择,每做一次贪心选择就将所求问题简化为规模更小的子问题 。)

  • 贪心的正确性:必须满足 贪心选择剩余兼容活动集 的最优解,组合在一起后,就会得到原问题的最优解。

PS:可以设计一个递归算法实现贪心策略,再将递归算法转化为迭代算法。因为迭代的效率更高。
PS:若在进行贪心选择中,不得不考虑枚举众多选择来找到最优,通常意味着可以改进贪心选择,使其更高效。通常的改进办法是:使用 合适的数据结构预处理

贪心算法设计步骤:

  • 将问题转化为:对一次选择后,只剩下一个子问题需要求解。(构造最优子结构)

  • 证明做出贪心选择后,原问题总是存在最优解,即其正确性。

例题:

删数问题:

策略:从前往后依次删除“山峰”。

证明
(因为证明过程太长,其中LaTex公式渲染较多,所以新开了一个随笔证明。)

代码如下:

#include<bits/stdc++.h>
#define MAXN 10000
using namespace std;
int main()
{
	string s;	int len,k;
	cin>>s>>k;	len=s.size();
	for(int t=1;t<=k;++t)
	{
		for(int i=0;i<len;++i)
		{
			if(s[i]>s[i+1])
			{
				for(int j=i;j<len;++j)
					s[j]=s[j+1];
				--len;
				break;
			}
		}
	}
	int i;	for(i=0; s[i]=='0' ;++i);
	if(i==len) cout<<"0";
	else
	{
		for(;i<len;++i)
			cout<<s[i];
	}
	return 0;
}

做题感悟:

  • 思考贪心策略时,可以先从最简单的入手,利用从 特殊到一般 的思想。
  • 对比较抽象的最优问题来说,利用反证法是一个不错的选择。即证明,没有方案比现在更优。

雇佣计划:

题目描述:

一位管理员项目的经理想要确定每个月需要的工人,他当然知道每月所需要的最少工人数。当他雇佣或解雇一个工人时,会有一此额外的支出。一旦一个工人被雇佣,即使他不工作,他也将得到工资。这位经理知道雇佣一个工人的费用,解雇一个工人的费用和一个工人的工资。现在他在考虑一个问题:为了把项目的费用控制在最低,他将每月雇佣或解雇多少个工人。

输入格式:

共三行。
第一行为月数N(不超过12)。
第二行含雇佣一个工人的费用,一个工人的工资和解雇一个工人的费用(<101)。
第三行含N个数,分别表示每月最少需要的工人数(<1001)每个数据之间用空格相隔。

输出格式:

仅一行,表示项目的最小总费用。

贪心策略:

分类讨论:
1.若工人缺少,雇佣。
2.若工人恰好,就不变,照常发工资。(这一条可以与1合并)
3.若工人冗余,有两种选择。要么解雇。要么供着。

其实这题的贪心证明很简单。就是比较哪一种方案的花费更少。并没有一个数学上的证明。

也告诉我们。有时贪心策略并不是一定是一个数学公式。

也可以是一个一个的枚举。

代码如下:

#include <bits/stdc++.h>
using namespace std;
int ned[100]; 
int main()
{
    int n,a,b,c,more;
	int choice1,choice2,k;
    scanf("%d%d%d%d",&n,&a,&b,&c);
    for(int i=1;i<=n;++i) scanf("%d",&ned[i]);
    int ans=(a+b)*ned[1];
    for (int i=2;i<=n;++i)
    {
        ans+=b*ned[i],more=ned[i]-ned[i-1];
        if(more>=0) ans+=a*more;
        else
        {
        	more=abs(more);
            for (int j=1;j<=more;++j)
            {
                choice1=0;
                for (k=i+1;k<=n;++k)
                    if (ned[k]>ned[i]) {choice1=a;break;}	choice1+=c;//dismiss
                choice2=b*(k-i);//employ
                if (choice1>=choice2) ++ned[i],ans+=b;
                else {ans+=c*(more-j+1);break;}
            }
        }
    }
    printf("%d
",ans);
    return 0;
}

做题感悟:

  • 枚举有时也是一种好方法哦。

三国游戏:

题目链接

贪心策略:人的一方必赢。选择时挑选最大的次大值。

证明:定义运算 ((a,b))(a)(b) 匹配的武力值。

设人取走的一名武将为 (m),那么与 (m) 匹配的武将中,必然存在两个人 (n_1,n_2),分别使得匹配后的武力值为最大值 (max=(m,n_1)),和次大值 (secmax=(m,n_2))

(m)满足的条件是,让(secmax)为最大的次大值。

根据题意,计算机的原则,是破坏人所选武将的最大值。所以,当人取走(m)后,计算机会取走(n_1),那么出于贪心规则,人就会取走 (n_2)

而如果计算机想要获得胜利,所取的武将,必须要大于(secmax),即必须取到(max),而要与(n_1)组成(max),就只有已经被人取走的(m),计算机无法取走。而剩下的值,一定都小于(secmax)

所以,人必胜,武将的最大武力值为(secmax)

代码如下:

#include <bits/stdc++.h>
int w[505][505];
int main()
{ 
    int n,m1,m2,m=0;	std::scanf("%d",&n);
    for(int i=1;i<n;++i)
    {
    	for(int j=i+1;j<=n;++j)
    	{
    		std::scanf("%d",&w[i][j]);
    		w[j][i]=w[i][j];
    	}
    }
    for(int i=1;i<=n;++i)
	{
		m1=m2=0;
		for (int j=1;j<=n;++j)
		{
			if (w[i][j]>m1)	m2=m1,m1=w[i][j];	
			else m2=std::max(m2,w[i][j]);
			m=std::max(m,m2);
		}
	} 
	std::printf("1
%d",m);
	return 0;
}
原文地址:https://www.cnblogs.com/cyl-oi-miracle/p/13492891.html