HDU5380 Travel with candy(单调队列 + 贪心)

/**
题意:
    有n+1个城市一直线上,现在从0号城市按顺序走到n号城市,从0号城市到i号城市没走一个单位距离需要消耗ai个糖果,
每个城市都可以买/卖糖果,价格分别是buyi和selli,selli<buyi。身上最多只能带C个糖果,在起点0号城市身上没有糖果,
问到达n号城市的最小花费。

思路:
考虑当前点, 每次将当前点的糖果补满,补满之前考虑之前剩余的糖果:
1. 上一个点到达当前点需要消耗xi的糖果, 消耗购买价格小的,总消费才能尽量小,剩余一些糖果

2. 糖果在之前城市的购买价格高于当前点的购买价格, 以原价售出(相当于不买)

3. 糖果在之前城市的购买价格低于当前点的购买价格,假设当前位置是r,该糖果是在l点购买的,
那么这个糖果有中可能:
(1) 该糖果在后面被消耗了
(2) 该糖果可以在之前的某个城市以一种更优的售出之后再到当前点购买等价交换之后使得购买价格更低
(3) 该糖果最终到达n号城市

1、2点用单调队列存储(购买价格由低到高)之后就可以解决,3中(1)(3)都是不用考虑的,考虑(2) :
设单调队列中的糖果购买价格最低的是在id这个位置,剩下res颗,在id购买价格是val,如果等价交换之后
在后面的消耗中消耗价格更低,那么有buy[i]-(sell_max[id, i]-val)<=val  =>  buy[i]<=sell_max[id, i]
(sell_max[l, r]是在区间[l,r]售出价格最高的地方),假设sell_max[id, i] = sell[x] (x这个位置取最大), 而
且有buy[i] <= sell_max[id, i]那么这个糖果就在x这个位置售出再用当前点的糖果补上,因为单调队列中的
价格从低到高排列而且位置中小到大排列, 从左往右处理下就行了
*/

#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e5 + 10;
using namespace std;

struct P {
    ll id, res, val;
    P() {}
    P(ll i, ll r, ll v) : id(i), res(r), val(v) {}
} que[maxn];
ll n, m, T, kase = 1;
ll b[maxn], s[maxn];
ll st[maxn][20], dis[maxn];

void init() {
    for(ll i = 1; (1 << i) <= n + 1; i++) {
        ll sz = (1 << i), s = sz >> 1;
        for(ll j = 0; j + sz - 1 <= n; j++) {
            ll x = j + s;
            st[j][i] = max(st[j][i - 1], st[x][i - 1]);
        }
    }
}

ll query(ll l, ll r) {
    ll tot = r - l + 1, ans = b[l], len = 0;
    for( ; (1 << len) <= tot; len++); len--;
    for( ; len >= 0; len--) if(l + (1 << len) - 1 <= r) {
        ans = max(ans, st[l][len]); l = l + (1 << len);
    }
    return ans;
}

int main() {
    scanf("%lld", &T);
    while(T--) {
        scanf("%lld %lld", &n, &m);
        for(ll i = 1; i <= n; i++) scanf("%lld", &dis[i]);
        for(ll i = n; i >= 1; i--) dis[i] = dis[i] - dis[i - 1];
        for(ll i = 0; i <= n; i++) { scanf("%lld %lld", &b[i], &s[i]); st[i][0] = s[i]; }
        init();
        ll tot = 0, l = 0, r = 0, ans = 0;
        for(ll i = 0; i <= n; i++) {
            tot -= dis[i];
            while(l < r && dis[i]) {
                if(que[l].res > dis[i]) {
                    que[l].res -= dis[i]; break;
                } else {
                    dis[i] -= que[l].res; l++;
                }
            }
            while(l < r && que[r - 1].val >= b[i]) {
                tot -= que[r - 1].res;
                ans += query(que[r - 1].id, i) * que[r - 1].res;
                r--;
            }
            while(l < r) {
                ll id = que[l].id, res = que[l].res, val = que[l].val;
                ll ds = query(id, i);
                if(ds < b[i]) break;

                ans += ds * res; tot -= res; l++;
            }
            que[r++] = P(i, m - tot, b[i]);
            ans -= (ll)(m - tot) * b[i];
            tot = m;
        }
        while(l < r) {
            ll id = que[l].id, res = que[l].res, val = que[l].val; l++;
            ans += query(id, n) * res;
        }
        printf("%lld\n", -ans);
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值