算法树洞之贪心专题


此文会不断更新

0 前言

贪心的题实在是把我虐得不轻,干脆做个专题出来

1 题目列表

1.1 最大不相交区间问题

题目: 看电视
"心路历程":
思路一;
1 对区间左端点由小到大排序
2 遍历,对每个区间左端点,尽量找离它最近的右端点
3 下一个右端点从>=上个区间左端点开始找
提交,错误
思考后发现问题出在第2步,即根本没法保证哪个右端点离当前区间左端点近,举例:
1 10
2 9
3 8
4 6
7 8
离1最近的右端点是6,排在很后面
因此,还不如按区间右端点从小到大排序!
思路二:
1 对区间右端点从小到大排序
2 遍历,对每个区间,如果>=上一次记录的右端点,结果就+1,并将上一次记录的右端点更新为当前右端点
提交,错误
猜测贪心策略不对,遂证明
证明如下:
如果这是错的,即在某次遍历cur中,不用找最小右端点,能获得更优的结果
那更优结果的这次遍历的右端点会更大,那下一次遍历的左端点就会更往右,有可能就会遗漏
所以"最优的"有可能遗漏,矛盾
所以我的思路是正确的
思路三:
既然策略正确,那提交不对就有几种可能了:
一:代码实现的问题
检查后排除
二:数组不够大的问题
觉得实在不太可能
三:有特殊数据,比如有区间左端点为负数
为此我调整了写法
四: 输入读入不对
果然,这是多组输入的题!错误原因多半就是它了!

提交后,终于过了
贴上最后代码:

#include<cstdio>
#include<algorithm>

using namespace std;

const int N = 100;

struct tv{
    int start;
    int end;
}arr[N];

bool cmp(tv a, tv b) {
    return a.end < b.end;
}

int main() {
    int n;

    while (scanf("%d", &n), n != 0) { // 其实判断的是后面
        for (int i=0; i<n; ++i) scanf("%d %d", &arr[i].start, &arr[i].end);
        sort(arr, arr+n, cmp);

        int cnt = 1, last = arr[0].end;
        for (int i=1; i<n; ++i) {
            if (arr[i].start < last) continue;
            cnt += 1;
            last = arr[i].end;
        }

        printf("%d\n", cnt);
    }

    return 0;
}

1.2 汽车加油问题

To Fill or Not to Fill

很恶心的一道题,需要考虑的特殊情况比较多,非常考验代码力,忍着牙疼写了半天代码把它A了:

#include<cstdio>
#include<algorithm>

using namespace std;

const int N = 500;

struct Station {
    double price;
    double dist;
}arr[N];

bool cmp(Station a, Station b) {
    return a.dist < b.dist;
}

int main() {
    double capacity, dist, gas_avg;
    int n_station;
    double tmp_price;
    double tmp_dist;

    scanf("%lf %lf %lf %d", &capacity, &dist, &gas_avg, &n_station);
    for (int i=0; i<n_station; ++i) {
        scanf("%lf %lf", &tmp_price, &tmp_dist);
        if (tmp_dist > dist) continue;
        arr[i].price  = tmp_price;
        arr[i].dist = tmp_dist;
    }
    sort(arr, arr+n_station, cmp);

    double interval = capacity * gas_avg; // 两加油站之间可以有的最大间隔,超出了无法到达
    double max_dist = 0;
    bool accessable = true;
    if (arr[0].dist != 0) {
        accessable = false;
    }else{
        for (int i=1; i<n_station; ++i) {
            if (arr[i].dist - arr[i-1].dist > interval) {
                accessable = false;
                max_dist = arr[i-1].dist + interval;
                break;
            } 
            
        }
        if (accessable) {
            if (arr[n_station-1].dist + interval < dist) { // 最后一个加油站离终点太远了也不行
                accessable = false;
                max_dist = arr[n_station-1].dist + interval;
            }
        }
    }
    if (!accessable) {
        printf("The maximum travel distance = %.2lf\n", max_dist);
        return 0;
    }

    double res = 0, cur_dist = 0, remain_gas = 0;
    int p=0;
    // 主要解决两个问题,当前站加多少油,怎么选择下一站
    while (p < n_station) { // p指向当前加油的站点
        if (p == n_station - 1) { // 特判一下特殊情况
            if (remain_gas) {
                // 如果油足够
                if (remain_gas * gas_avg + cur_dist >= dist) {
                    break;
                } else { // 油不够
                    res += (dist - cur_dist - remain_gas * gas_avg) / gas_avg * arr[p].price;
                    break;
                }
            } else {
                res += (dist - cur_dist) / gas_avg * arr[p].price;
                break;
            }
        }

        int pnext = p+1, pi = pnext;
        while (pi < n_station && arr[pi].dist - arr[p].dist <= interval) {
            if (arr[pi].price < arr[p].price) { // 只要比当前站价钱便宜就行
                pnext = pi;
                break;
            }
            ++pi;
        }

        if (arr[p].price > arr[pnext].price) { // 下一站更便宜, 买油只买到下一站就行了
            if (remain_gas){
                if (remain_gas * gas_avg + cur_dist >= arr[pnext].dist) {
                    remain_gas -= (arr[pnext].dist - arr[p].dist) / gas_avg;
                }else{
                    res += (arr[pnext].dist - cur_dist - remain_gas * gas_avg) / gas_avg * arr[p].price;
                    remain_gas = 0;
                }
            } else {
                res += (arr[pnext].dist - cur_dist) / gas_avg * arr[p].price;
            }
        } else { // 下一站价钱更贵
            // 看一下[下下站](如果有的话)
            if (pnext == n_station - 1) {
                // 买够油(买满或者买到撑到最后)
                double need_capacity = (dist - cur_dist) / gas_avg;
                if (need_capacity >= capacity) {
                    // 把油箱买满
                    res += (capacity - remain_gas) * arr[p].price;
                    remain_gas = capacity - (arr[pnext].dist - cur_dist) / gas_avg;
                } else {
                    res += (need_capacity - remain_gas) * arr[p].price;
                    remain_gas = 0;
                }
            } else { // pnext不是最后一站,pnext2有可能是
                int pnext2 = pnext+1, pi2 = pnext2;
                while (pi2 < n_station && arr[pi2].dist - arr[pnext].dist <= interval) {
                    if (arr[pi2].price < arr[pnext].price) {
                        pnext2 = pi2;
                        break;
                    }
                    ++pi2;
                }
                if (arr[pnext2].price < arr[p].price) { // 如果下下站更便宜
                    // 买油买到下下站或者加满
                    if (arr[pnext2].dist - arr[p].dist >= interval) {
                        res += (capacity - remain_gas) * arr[p].price;
                        remain_gas = capacity - (arr[pnext].dist - arr[p].dist) / gas_avg;
                    } else {
                        res += ((arr[pnext2].dist - arr[p].dist) / gas_avg - remain_gas) * arr[p].price;
                        remain_gas = (arr[pnext2].dist - arr[pnext].dist) / gas_avg;
                    }
                } else { // 越来越贵, 那就买够油
                    if (arr[p].dist + interval >= dist) {
                        res += ((dist - arr[p].dist) /  gas_avg - remain_gas) * arr[p].price;
                        remain_gas = (dist - arr[pnext].dist) / gas_avg;
                    } else {
                        res += (capacity - remain_gas) * arr[p].price;
                        remain_gas = capacity - (arr[pnext].dist - arr[p].dist) / gas_avg;
                    }
                }
            }
        }

        // 转移到下一站
        p = pnext;
        cur_dist = arr[p].dist;
    }

    printf("%.2lf\n", res);

    return 0;
}

1.3 多个数字串拼接为最小数字串问题

此题比较经典
Recover the Smallest Number

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const int N = 1e4;
bool cmp(string a, string b) { return a + b < b + a; }
string str[N];
int main() {
    int n, p = 0;
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) cin >> str[i]; // cin不会读入空格, scanf会
    sort(str, str + n, cmp);
    while (p < n && stoi(str[p]) == 0) ++p;
    if (p < n) {
        cout << stoi(str[p]);
        for (int i=0; i<n; ++i) if (i != p) cout << str[i];
    } else cout << 0;
    return 0;
}

难处有二:
1 对于排序,不能用字典序比较,而要用两种拼接方式的字典序比较(比较巧妙)
2 只有第一个0开头的串需要去掉0,剩下的都不能去掉0, 需要仔细认真反复读题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值