二分与贪心算法(POJ4110圣诞老人的礼物,POJ 3104 烘晾衣服)

1.二分查找
前提是: 已经排序好的序列
在查找元素时, 首先与序列中间的元素进行比较
• 如果大于这个元素, 就在当前序列的后半部分继续查找
• 如果小于这个元素, 就在当前序列的前半部分继续查找
• 直到找到相同的元素, 或者所查找的序列范围为空为止
伪码描述
left = 0, right = n -1 while (left <= right)
mid = (left + right) / 2
case
x[mid] < t:left = mid + 1;
x[mid] = t:p = mid; break;
x[mid] > t:right = mid -1;
return -1;
注意:我们在更新left和更新right的时候,一定是加1和减1, 不要只是更新mid,否则的话呢,整个程序会处在一个死循环当中。

2.贪心算法
问题求解时, 总是做出在当前看来是最好的选择
• 即不保证全局最优,仅是在某种意义上的局部最优解

贪心算法设计的关键是贪心策略的选择

自顶向下计算
• 通过贪心选择, 将原问题规约为子问题

贪心的基本思想
1->建立数学模型来描述问题
2->把求解的问题分成若干个子问题
3->对每一子问题求解, 得到子问题的局部最优解
4->把子问题的解局部最优解合成原来解问题的一个解

例题1,POJ4110圣诞老人的礼物
• 圣诞节来临了, 在城市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

解题思路
装尽可能多的糖果 贪心!!!
• 先放总重量大的糖果? 可是,重量大的不一定价值高
• 先放总价值高的糖果? 但是呢,总价值高的重量可能很大
• 如何贪心地选择最佳的糖果?如果可以选择 “单位价值最大”那就可以啦

由于每箱糖果可以任意组合,所以一定可以放入所有糖果,或者驯鹿能承受最大容量被放满
• 因此放入的总重量是确定的
只用选择放入单位重量价值高的糖果
• 将糖果按单位重量价值从高到低排序, 依次放入,直到放满或者放完为止

#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;

#define MAXN 110


//每箱糖果
struct Box
{
int v, w; //价值和重量
double density; //单位重量的价值
};

int n, w;
Box boxes[MAXN];
double totw, totv;

//箱子按单位重量价值排序
bool operator < (const Box &a, const Box &b)
{
    return a.density < b.density;
}

int main()
{
    cin >> n >> w;
    for ( int i = 0; i < n; ++i )
    {
        cin >> boxes[i].v >> boxes[i].w;
        boxes[i].density = 1.0 * boxes[i].v / boxes[i].w ;//计算单位重量的价值
    }
        sort(boxes, boxes+n); //按单位重量的价值排序
        totw = totv = 0;
        for (int i = n - 1;i >= 0;--i) //按单位重量的价值从大到小依次放入
            if (totw + boxes[i].w <= w)
            { //未放满
                totw += boxes[i].w;
                totv += boxes[i].v;
            }
            else
            { //已放满
                totv += boxes[i].density * (w - totw); totw = w;
                break;
    }
      printf("%.1lf\n", totv);
         return 0;
}

例题2,POJ 3104 烘晾衣服
问题描述
现有 n 件衣服需要烘干,每件衣服的含水量为 ai
• 如果自然晾干, 每分钟含水量减少 1
• 如果使用烘干机烘干, 每分钟含水量减少 k (直至为0)
只有一台烘干机, 每次只能烘干一件衣服 且一次至少使用1分钟 求使所有衣服含水量为0的最少时间是多少
程序输入
• 输入包含三行
• 第一行是一个整数 n (1 ≤ n ≤ 100000),
表示衣服的数量
• 第二行有n个整数,
分别表示各件衣服的含水量 ai (1 <= ai <= 10^9)
• 第三行是一个整数 k (1 <= k <= 10^9),
表示烘干机1分钟减少的水量
程序输出
• 输出一行, 表示最少需要的时间
样例输入
3
23 9 5
样例输出
3

解题思路
要求最小的时间
问题转换:
最小值问题 -> 判定性问题
• 判断在时间X内,是否能晾干/烘干所有的衣服
这个问题满足单调性的条件对时间X进行二分来解决!对于给定的时间X, 依次判断每一件衣服 i
• 如果ai<=X,则该衣服可以自然烘干
• 否则说明需要烘干机,
因为多用 1分钟 烘干机, 可以多减少 (k-1) 的水量,至少需要 ceil((X – ai) / (k-1)) 分钟的烘干机
如果所有衣服需要烘干机的时间总和 <= X 则说明时间X是可行的, 否则说明不可行

重点函数分析 -> 判断X是否可行
bool check(X)
{
当前需要烘干机时间 = 0;
for ( 所有的衣服 )
{
if ( 第i件衣服含水量 > X )
{
当前总计需要烘干机时间 += 第i件衣服至少需要的烘干机时间;
}
}
//判断最后的烘干机时间是否小于等于X
return 当前需要烘干机时间 <=X
}

#include <stdio.h>
#include <iostream>
using namespace std;
#define MAXN (100000+10)
int n; //n件衣服
int l, r, mid; //用于判定时间X,所考虑的左右及中值
int k; //烘干机1分钟减少的水量
int a[MAXN];

//判断时间为ans是否可行
bool check(int ans)
{
int now = 0; //需要烘干机的时间
for (int i = 0; i < n; ++i )
if (a[i] > ans)
{
    now += (a[i] - ans - 1) / (k - 1) + 1; //第i件衣服需要烘干机的时间,注意是(k-1)
        // [a/k] = (a-1)/k + 1
    if (now > ans)
        return false;
}
return true;
}
int main()
{
    cin >> n;
    l = 0; r = 0;
    for (int i = 0;i < n;++i)
    {
        cin >> a[i];
        if (a[i] > r) r = a[i];
        
    }
    cin >> k;
    if (k == 1)
        cout << r << endl; //k=1, 直接输出, 避免除0的情况
    else {
            //二分答案,判断可行性
        while (l <= r)
        {
        mid = (l + r) / 2;
        if (check(mid)) r = mid - 1;
        else l = mid + 1;
    }
        cout << l << endl;
}
return 0;
} 
    原文作者:贪心算法
    原文地址: https://blog.csdn.net/ancientear/article/details/79242933
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞