贪心算法的定义与性质:
-
贪心算法是通过做出一系列 选择 来求出问题的最优解。在每一个决策点,它作出在 当前看来最好 的选择。
-
(也就是说贪心算法并 不从整体最优上加以考虑 ,它所作出的选择只是在某种意义上的 局部最优选择 。大概也是贪心算法名字的由来)
-
(贪心算法进行决策时, 可能依赖于以往所做出的选择,但决不依赖于将来所做的选择,或者子问题的解 。)
-
-
贪心算法必须满足 最优子结构 。与动态规划不同,贪心的最优子结构,必须满足 选择后只留下一个子问题 。
-
(从最优子结构的角度说:相当于特殊的动态规划。动态规划的选择后往往会产生多个子问题。)
-
(正是由于这种差异,动态规划算法通常以自底向上的方式解各子问题,而 贪心算法则通过以自项向下的方式 进行,以 迭代 的方式做出相继的贪心选择,每做一次贪心选择就将所求问题简化为规模更小的子问题 。)
-
-
贪心的正确性:必须满足 贪心选择 与 剩余兼容活动集 的最优解,组合在一起后,就会得到原问题的最优解。
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;
}