P1752 点菜 题解

广告

题意

n n n 个人,每个人每周至多选一道菜吃掉,求吃完 m m m 道菜的最小时间或无解。

m m m 道菜有美味值和价格两个参数, n n n 个人中 p p p 个人只能吃美味值大于等于一定值的菜, q q q 个人只能吃价格小于等于一定值的菜,其余人没有限制。

解法

可以发现如果 x x x 周能吃完则 x + 1 x + 1 x+1 周也能吃完;如果 x x x 周吃不完则 x − 1 x - 1 x1 周也吃不完,说明答案具有单调性。于是考虑二分答案,check 的部分则贪心检查。

根据题意,一个可行的贪心方案是:总是让有限制的人群优先选择菜,且限制宽松的人总是吃掉条件更苛刻的(例如更贵的、更难吃的)菜,从而让限制较大的人产生更大的贡献。

这里我们选择优先考虑挑剔的 p p p 个人。设第 i i i 个挑剔的人的美味值下限为 P i P_i Pi,第 j j j 个贫穷的人的价格上限为 Q j Q_j Qj;第 k k k 道菜的美味值为 A k A_k Ak,价格为 B k B_k Bk

先将挑剔的人的美味值下限 P P P 从大到小排序,将 m m m 道菜按照美味值从大到小排序。对于第 k k k 道菜,如果第 i i i 个挑剔的人能接受这道菜,则把这道菜加入到一个待处理菜的队列里(原因后面会讲),然后考虑第 ( k + 1 ) (k+1) (k+1) 道菜;如果不能接受,则让他尽可能吃光这个队列里的菜,然后考虑第 ( i + 1 ) (i + 1) (i+1) 个人。

做法原因:因为对原数组进行过排序,第 i i i 个人不能接受的菜,前 ( i − 1 ) (i-1) (i1) 个人一定无法接受;同理第 i i i 个人能接受的菜,后 ( p − i ) (p-i) (pi) 个人一定可以接受。对于第 i i i 个人,当前队列里的菜都是他能吃的,后面的人也能吃。这就保证了每个人在限制内吃到最多的菜,对答案贡献最大。

肯定有更简洁的方法,这种方法只是写起来清晰一些o.O

考虑完所有的菜后,如果还有挑剔的人没有被考虑,此时队列里的菜他们肯定都能吃,所以让他们去尽可能吃光队列里的菜。最后队列里剩下的菜交给穷人和普通人处理。

将贫穷的 q q q 个人的价格上限 Q Q Q 也从大到小排序。这里因为是上限,第 j j j 个人能吃的菜,前 ( j − 1 ) (j-1) (j1) 个人都能吃;反之则后 ( q − i ) (q-i) (qi) 个人一定吃不起。对于第 j j j 个贫穷的人,如果他不能接受队列中第 k k k 贵的菜,而前面的人又都吃了尽可能多的才,则说明这道菜只能由普通人吃,将其弹出并记录,然后考虑队列中第 ( k + 1 ) (k+1) (k+1) 贵的菜;否则当前队列比第 k k k 贵的菜便宜的菜他都吃得起,就让他尽可能去吃这些菜,然后考虑第 ( j + 1 ) (j+1) (j+1) 个贫穷的人。

考虑完所有贫穷的人后,队列里剩下的菜和被记录的菜由剩下的 ( n − p − q ) (n-p-q) (npq) 个普通人解决。最后判断普通人能否吃完即可。

实现

考虑挑剔的人时可以用两个指针,分别记录当前考虑到了哪道菜、哪个人,待处理队列使用优先队列(按照价格从大到小)。假设当前二分的答案为 w e e k week week,则每个人最多可以吃掉 w e e k week week 道菜。最后可以用一个变量 t o t tot tot 记录有几道菜没被吃而被弹出(被记录的菜),设最终优先队列里还剩下 s i z siz siz 道菜,如果 t o t + s i z ≤ w e e k ( n − p − q ) tot+siz\leq week(n-p-q) tot+sizweek(npq),则当前答案 w e e k week week 合法。

设有 m m m 道菜( m ≤ 2 × 1 0 5 m\leq2\times10^5 m2×105),二分答案 O ( log ⁡ m ) O(\log m) O(logm),贪心部分 O ( m log ⁡ m ) O(m\log m) O(mlogm),总时间复杂度 O ( m log ⁡ 2 m ) O(m\log^2m) O(mlog2m)

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 50005,maxm = 2e5 + 5;
ll P[maxn],Q[maxn]; int q,n,m,p,ans = -1; pair<ll,ll> M[maxm];
bool check(int week) {
    int j = 1,tot = 0; priority_queue<int> pq;
    for (int i = 1;i <= m;i ++) {
        for (;j <= p && P[j] > M[i].first;j ++)
            for (int k = 1;k <= week;k ++)
                if (pq.empty()) break;
                else pq.pop();
        pq.push(M[i].second);
    }
    for (;j <= p;j ++)
        for (int k = 1;k <= week;k ++)
            if (pq.empty()) break;
            else pq.pop();
    for (int i = 1;i <= q;i ++) {
        while (!pq.empty() && Q[i] < pq.top())
            pq.pop(), tot ++;
        for (int k = 1;k <= week;k ++)
            if (pq.empty()) break;
            else pq.pop();
    }
    return (n - p - q) * week >= tot + pq.size();
}
int main() {
    scanf("%d%d%d%d",&n,&m,&p,&q);
    for (int i = 1;i <= m;i ++)
        scanf("%lld%lld",&M[i].first,&M[i].second);
    for (int i = 1;i <= p;i ++) scanf("%lld",&P[i]);
    for (int i = 1;i <= q;i ++) scanf("%lld",&Q[i]);
    sort(M + 1,M + m + 1); reverse(M + 1,M + m + 1);
    sort(P + 1,P + p + 1); reverse(P + 1,P + p + 1);
    sort(Q + 1,Q + q + 1); reverse(Q + 1,Q + q + 1);
    int l = 1,r = m,mid;
    while (l <= r) {
        mid = l + r >> 1;
        if (check(mid)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }
    printf("%d",ans);
    return 0;
}
  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值