C++贪心算法与动态规划

先来看一道题:

———————————————————————————————————————————

来源:
2018第二十四届全国青少年信息学奥林匹克联赛(NOIP)初赛提高组5 - 2

题目描述:
一只小猪要买N件物品(N不超过1000)。
它要买的所有物品在两家商店里都有卖。第i件物品在第一家商店的价格是a[i],在第二家商店的价格是b[i],两个价格都不小于0且不超过10000。如果在第一家商店买的物品的总额不少于50000,那么在第一家店买的物品都可以打95折(价格变为原来的0.95倍)。
求小猪买齐所有物品所需最少的总额。

输入格式:
第一行一个数N。接下来N行,每行两个数。第i行的两个数分别代表a[i],b[i]。

输出格式:
输出一行一个数,表示最少需要的总额,保留两位小数。

———————————————————————————————————————————
编程语言-贪心算法与动态规划
贪心算法(greedy algorithm)是一种常见的解决优化问题的算法,其基本思想是在问题的每个决策阶段,都选择当前看起来最优的选择,即贪心地做出局部最优的决策,以期获得全局最优解。贪心算法简单、高效,在许多实际问题中有着广泛的应用。
贪心算法和动态规划都常用于解决优化问题。它们之间存在一些相似之处,比如都依赖最优子结构性质,但工作原理不同。动态规划会根据之前阶段所有决策来考虑当前决策,并使用过去子问题的解来构建当前子问题的解。贪心算法不会考虑过去的决策,而是一路向前地进行贪心选择,不断缩小问题范围,直至问题被解决。
我们先通过例题“零钱兑换”了解贪心算法的工作原理。给定n种硬币,每种硬币可以重复选取,问能够凑出目标金额的最少硬币数量。对于给定的目标金额:131,我们贪心地选择不大于且最接近它的硬币,不断循环该步骤,直至凑出目标金额为止。
但是,我们再看下面这个问题:现在问题变了,需要找给顾客41分钱,现在的货币只有25分、20分、10分、5分和1分五种硬币,该怎么办?
按照贪心算法的三个步骤:
①25分,局部最优化原则,先找给顾客25分;
②此时,41 - 25 = 16分,还需要找给顾客10分,然后5分,然后1分;
③最终,找给顾客一个25分,一个10分,一个5分,一个1分,共四枚硬币。
是不是觉得哪里不太对,如果给他2个20分,加一个1分,三枚硬币就可以了呢?^_^;
总结,贪心算法的优点:简单,高效,省去了为了找最优解可能需要穷举操作,通常作为其它算法的辅助算法来使用;缺点:不从总体上去考虑其它可能情况,每次选取局部最优解,不再进行回溯处理,所以很少情况下得到最优解。
上边这道NOIP的小猪购物的真题,实际上是同时结合了贪心算法和动态规划的思想。
贪心部分:程序首先采用了一种简单的贪心策略,根据a[i]和b[i]的大小关系,先做出初步的购买决策:如果第i件物品在第一家商店的价格小于等于第二家商店(b[i]的价格,或者第i件物品在第一家商店购买后能享受95折并且价格更低,那么就选择在第一家商店购买(total_a累加)。否则,就选择在第二家商店购买(total_b累加)。这个步骤可以看作一种贪心策略,即先选择最优的购买方式(直接比较两个商店的价格或考虑折扣的情况),从而快速得到一个初步的解答。
动态规划部分:在贪心选择之后,如果第一家商店的消费总额total_a达不到50000,程序进一步通过动态规划优化当前的方案。动态规划的部分使用了0 - 1背包问题的思想:f[i]表示在总和为i的情况下,去第二家商店购买某些物品所花费的最小金额。程序通过遍历每件在第二家商店购买的商品(!put_a[i])来更新状态:对于每件商品,要么选择不放进背包(即不从第一家商店购买),要么选择将其转移到第一家商店购买,逐步更新f数组。在背包转移过程中,程序尝试找到可以通过转移部分商品到第一家商店,使得第一家商店的消费额达到或超过50000,并计算此时的总费用是否更优。

答案:

#include 
#include 
using namespace std;
const int Inf = 1000000000;
const int threshold = 50000;
const int maxn = 1000;
int n, a[maxn], b[maxn]; //表示价格
bool put_a[maxn]; //put_a[i]表示第i个物品是不是在a商场买的
int total_a, total_b;  //表示在a商场和b商场耗费的金钱
double ans;
int f[threshold];  //这个是重点:f[i]表示从当前前缀状态下,买总价格为i的物品,所需要去b商场的最小花费
int main() {
scanf("%d", &n);
total_a = total_b = 0;
for (int i = 0; i < n; ++i) {
scanf("%d%d", a + i, b + i);
if (a[i] <= b[i]) total_a += a[i];
else total_b += b[i];
}
ans = total_a + total_b; //不考虑优惠,直接算一遍答案
total_a = total_b = 0;
for (int i = 0; i < n; ++i) { //考虑优惠且必须优惠(因为不优惠的状况考虑过了),只要a[i]*0.95=50000 ) { //如果说total_a已经达到优惠所需最小花费,那么直接输出答案(这时把任何b商场里的东西换到a商场买不会更优)
    printf("%.2f", total_a * 0.95 + total_b);
    return 0;
}
f[0] = 0;
for (int i = 1; i < threshold; ++i)
    f[i] = Inf;
int total_b_prefix = 0;
for (int i = 0; i < n; ++i)
    if (!put_a[i])
{ //如果是去b商场买的就考虑背包转移
    total_b_prefix += b[i]; //当前去b商场买的物品所花费总代价
    for (int j = threshold - 1; j >= 0; --j) {
        if ( total_a + j + a[i] >= threshold && f[j] != Inf)  //如果说可以转移
            ans = min(ans, (total_a + j + a[i]) * 0.95 + total_b + f[j] - total_b_prefix ); //判断是否更优
        //当前去a商场的总花费加上背包体积以及当前物品,乘以折扣,在加上去b商场购买的总价值,是否小于于ans
        f[j] = min(f[j] + b[i], j >= a[i] ? f[j-a[i]] : Inf); //背包转移
        //考虑当前的j大小的背包是加上代价b[i]更优还是直接靠f[j-a[i]]转移更优(就是考虑当前的背包是加上代价还是状态转移)
    }
}
printf("%.2f", ans);
return 0;



 

你可能感兴趣的:(C++,贪心,编程,c++,贪心算法,动态规划)