贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择,而不考虑后续可能造成的影响。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。
贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
在贪心算法的应用当中,往往都会和排序联系起来,所以想要熟练使用贪心算法则必须首先能够熟练地应用排序。而由于贪心算法采取的是局部最优策略,故如果使用贪心算法之前往往需要证明算法的正确性,即证明在此题目/事件中,在每一步采取局部最优策略会得到整体最优的结果。
与此算法相对应的是后续我们会学到的动态规划,在动态规划中我们则必须要考虑一个选择对后续的影响。
贪心算法的应用非常广泛,很多其他的算法都需要贪心算法来作为基础。例如:哈夫曼编码、 K r u s k a l Kruskal Kruskal算法、 P r i m Prim Prim算法、 A ∗ A^* A∗算法等等。所以学好贪心算法是非常重要。
下面我们将以洛谷上的题目P1208为例,讲解贪心算法的最简单的使用方式。
显然按照题目意思,我们只需要将牛奶价格排序好,然后从价格低的开始买,如果此价格的牛奶已经没了,那么才购买价格更高的牛奶,一直买到满足公司收购的量为止即可。
本题目的贪心非常浅显易懂,这里不再给出证明。
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const ll maxn=5010;
ll n,m,ans=0;
struct node
{
ll per,num;
}farmer[maxn];
bool cmp(node x,node y)
{
if(x.per!=y.per)
return x.per<y.per;
else
return x.num>y.num;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=m;i++)
scanf("%lld%lld",&farmer[i].per,&farmer[i].num);
sort(farmer+1,farmer+1+m,cmp);
ll pos=1;
while(n)
{
if(farmer[pos].num!=0)
{
farmer[pos].num--;
ans+=farmer[pos].per;
n--;
}
else
pos++;
}
printf("%lld\n",ans);
return 0;
}
下面我们将以洛谷上的题目P1094为例,讲解贪心算法的进一步应用,并给出证明的思路。
根据我们前面所讲到的点,将使用反证法证明
设价格总和上限为 X X X,现有四个物品的价格为 v a , v b , v c , v d v_a,v_b,v_c,v_d va,vb,vc,vd,按从小到大排序
若 v a + v d ≤ X , v b + v c ≤ X v_a+v_d\leq X,v_b+v_c\leq X va+vd≤X,vb+vc≤X,此为贪心法得到的分组,最少分为两组。下面改变组合方式,因为 v a + v c ≤ X v_a+v_c\leq X va+vc≤X显然成立,所以仅考虑另一组的情况。
若 v a + v d > X v_a+v_d> X va+vd>X则无论如何都只能分为一组
若 v a + v d ≤ X , v b + v c > X v_a+v_d\leq X,v_b+v_c> X va+vd≤X,vb+vc>X。下面考虑 v a + v c , v b + c d v_a+v_c,v_b+c_d va+vc,vb+cd这一种组合方式,显然 v b + v d > X v_b+v_d>X vb+vd>X,所以无论怎么分仍然只有一组。
可以看到,非贪心的情况有可能导致答案数变少。例如 v a = 1 , v b = 5 , v c = 6 , v d = 8 , X = 11 v_a=1,v_b=5,v_c=6,v_d=8,X=11 va=1,vb=5,vc=6,vd=8,X=11
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const ll maxn=1000010;
ll num[maxn],ans;
int main()
{
ll n,w;
scanf("%lld%lld",&w,&n);
for(ll i=0;i<n;i++)
scanf("%lld",&num[i]);
sort(num,num+n);
ll z=0,y=n-1;
while(z<=y)
{
if(num[z]+num[y]<=w)
{
ans++;
z++;
y--;
}
else
{
y--;
ans++;
}
}
printf("%lld\n",ans);
return 0;
}
P1094 [NOIP2007 普及组] 纪念品分组
P1208 [USACO1.3]混合牛奶 Mixing Milk
P3817 小A的糖果
P5019 [NOIP2018 提高组] 铺设道路
P1056 [NOIP2008 普及组] 排座椅
P1199 [NOIP2010 普及组] 三国游戏