题意
有 n n n 个人,每个人每周至多选一道菜吃掉,求吃完 m m m 道菜的最小时间或无解。
这 m m m 道菜有美味值和价格两个参数, n n n 个人中 p p p 个人只能吃美味值大于等于一定值的菜, q q q 个人只能吃价格小于等于一定值的菜,其余人没有限制。
- 原题链接:P1752 点菜
解法
可以发现如果
x
x
x 周能吃完则
x
+
1
x + 1
x+1 周也能吃完;如果
x
x
x 周吃不完则
x
−
1
x - 1
x−1 周也吃不完,说明答案具有单调性。于是考虑二分答案,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) (i−1) 个人一定无法接受;同理第 i i i 个人能接受的菜,后 ( p − i ) (p-i) (p−i) 个人一定可以接受。对于第 i i i 个人,当前队列里的菜都是他能吃的,后面的人也能吃。这就保证了每个人在限制内吃到最多的菜,对答案贡献最大。
肯定有更简洁的方法,这种方法只是写起来清晰一些o.O
考虑完所有的菜后,如果还有挑剔的人没有被考虑,此时队列里的菜他们肯定都能吃,所以让他们去尽可能吃光队列里的菜。最后队列里剩下的菜交给穷人和普通人处理。
将贫穷的 q q q 个人的价格上限 Q Q Q 也从大到小排序。这里因为是上限,第 j j j 个人能吃的菜,前 ( j − 1 ) (j-1) (j−1) 个人都能吃;反之则后 ( q − i ) (q-i) (q−i) 个人一定吃不起。对于第 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) (n−p−q) 个普通人解决。最后判断普通人能否吃完即可。
实现
考虑挑剔的人时可以用两个指针,分别记录当前考虑到了哪道菜、哪个人,待处理队列使用优先队列(按照价格从大到小)。假设当前二分的答案为 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+siz≤week(n−p−q),则当前答案 w e e k week week 合法。
设有 m m m 道菜( m ≤ 2 × 1 0 5 m\leq2\times10^5 m≤2×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;
}