ZOJ3699 Dakar Rally 单调队列

题意:比赛的时候想了各种的贪心方案,最后还是没有把这题搞出来......废话不多说,题目给定了一条条给定了顺序的路径,这些路径是后面要一一走过的,每条路径有一个长度,单位长度消耗汽油的量以及该条公路上加油站的汽油单价。告诉你路径条数N,油箱容积K,问如何安排加油是的行走完所有路径的花费最少。

解法:该题有一个很巧妙的解法就是每到一个油站都加满油箱,队列里面保留了走过前面路径后保留的最便宜的油,每次从队列中取出最便宜的油行进这一段路程。在维持一个汽油价格单调递增时,具体过程如下:

1.当队尾不为空时,每次从队尾向前遍历,如果元素单价高于当前路线加油站的汽油单价,替换之,知道遇到价格比其低的汽油或者队列为空位置。
2.从队列中从前往后选择合适的汽油来行进该段路劲,并且更新价格。

代码如下:

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;

int N, K;
typedef long long LL;

struct road {
    int len, cpm, pri;
}e[100005];

struct gas {
    int vol, pri;
    gas(int v, int p) : vol(v), pri(p) {}
    gas() {}
};

deque<gas>dq;
int vol;

void solve() {
    LL ret = 0;
    vol = 0;
    while (!dq.empty()) {
        dq.pop_front();
    }
    for (int i = 0; i < N; ++i) {
        while (!dq.empty() && dq.back().pri > e[i].pri) {
            vol -= dq.back().vol;
            dq.pop_back();
        }
        dq.push_back(gas(K-vol, e[i].pri));
        // 以上维护好一个单调递增的汽油序列
        int nd = e[i].len * e[i].cpm; // 需求一定是一个不大于K的数值
        vol = K - nd;
        while (nd) {
            gas & cur = dq.front(); // 引用队首的汽油
            int Min = min(nd, cur.vol);
            cur.vol -= Min, nd -= Min;
            ret += 1LL * Min * cur.pri;
            if (!cur.vol) dq.pop_front();
        }
    }
    printf("%lld\n", ret);
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d %d", &N, &K);
        bool flag = true;
        for (int i = 0; i < N; ++i) {
            scanf("%d %d %d", &e[i].len, &e[i].cpm, &e[i].pri);
            if (1LL * e[i].len * e[i].cpm > K) { // 可能会溢出
                flag = false;
            }
        }
        if (!flag) {
            puts("Impossible");
            continue;
        }
        solve();
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Lyush/p/3066626.html