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;
}
题目列表 User Avatar 主页 题库 网校 训练题单 比赛 评测记录 讨论区 文章广场 更多功能 图片上传 云剪贴板 主题商店 咕值排名 等级分排名 洛谷有题 工单/反馈 相关链接 帮助中心 联系我们 社区规则 陶片放逐 管理名单 tttyy01 个人中心 用户设置 练习情况 我的题库 我的专栏 收藏夹 我的工单 锁定登出 P1164 小A点菜 提交答案加入题单复制题目 提交 230.52k 通过 110.78k 时间限制 1.00s 内存限制 512.00MB 题目编号 P1164 提供者 洛谷 难度 普及− 历史分数 暂无 提交记录 查看题解 题目反馈 标签 洛谷原创 相关讨论进入讨论版 推荐题目 复制 Markdown 中文 进入 IDE 模式 题目背景 uim 神犇拿到了 uoi 的 ra(镭牌)后,立刻拉着基友小 A 到了一家……餐馆,很低端的那种。 uim 指着墙上的价目表(太低级了没有菜单),说:“随便点”。 题目描述 不过 uim 由于买了一些书,口袋里只剩 M 元 (0<M≤10000)。 餐馆虽低端,但是菜品种类不少,有 N 种 (1≤N≤100),第 i 种卖 a i ​ 元 (0<a i ​ ≤1000)。由于是很低端的餐馆,所以每种菜只有一份。 小 A 奉行“不把钱吃光不罢休”的原则,所以他点单一定刚好把 uim 身上所有钱花完。他想知道有多少种点菜方法。 由于小 A 肚子太饿,所以最多只能等待 1 秒。 输入格式 第一行两个整数 N 和 M,分别表示菜品种类和 uim 身上的钱数。 第二行 N 个正整数 a i ​ (可能有重复),用空格隔开,分别表示每种菜的价格。 输出格式 一个正整数,表示点菜方案数,保证答案的范围在 [0,2 31 −1] 之内(不超过 C/C++的 int 范围)。 输入输出样例 输入 #1复制 4 4 1 1 2 2 输出 #1复制 3 说明/提示 2020.8.29,增添一组 hack 数据 by @yummy 加入题单 操作 加入做题计划 加入个人题单 加入团队题单 保存 复制题目 目标团队 获取团队中... 保存
11-24
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值