xdm,时隔 2-1 个月,来补题解了。
解法
可以发现如果 $x$ 周能吃完则 $x+1$ 周也能吃完;如果 $x$ 周吃不完则 $x−1$ 周也吃不完,说明答案具有单调性。于是考虑二分答案,check 的部分则贪心检查。
根据题意,一个可行的贪心方案是:总是让有限制的人群优先选择菜,且限制宽松的人总是吃掉条件更苛刻的(例如更贵的、更难吃的)菜,从而让限制较大的人产生更大的贡献。
这里我们选择优先考虑挑剔的 $p$ 个人。设第 $i$ 个挑剔的人的美味值下限为 $P_i$,第 $j$ 个贫穷的人的价格上限为 $Q_j$;第 $k$ 道菜的美味值为 $A_k$ ,价格为 $B_k$。
先将挑剔的人的美味值下限 $P$ 从大到小排序,将 $m$ 道菜按照美味值从大到小排序。对于第 $k$ 道菜,如果第 $i$ 个挑剔的人能接受这道菜,则把这道菜加入到一个待处理菜的队列里(原因后面会讲),然后考虑第 $(k+1)$ 道菜;如果不能接受,则让他尽可能吃光这个队列里的菜,然后考虑第 $(i+1)$ 个人。
做法原因:因为对原数组进行过排序,第 $i$ 个人不能接受的菜,前 $(i−1)$ 个人一定无法接受;同理第 $i$ 个人能接受的菜,后 $(p−i)$ 个人一定可以接受。对于第 $i$ 个人,当前队列里的菜都是他能吃的,后面的人也能吃。这就保证了每个人在限制内吃到最多的菜,对答案贡献最大。
肯定有更简洁的方法,这种方法只是写起来清晰一些。
考虑完所有的菜后,如果还有挑剔的人没有被考虑,此时队列里的菜他们肯定都能吃,所以让他们去尽可能吃光队列里的菜。最后队列里剩下的菜交给穷人和普通人处理。
将贫穷的 $q$ 个人的价格上限 $Q$ 也从大到小排序。这里因为是上限,第 $j$ 个人能吃的菜,前 $(j−1)$ 个人都能吃;反之则后 $(q−i)$ 个人一定吃不起。对于第 $j$ 个贫穷的人,如果他不能接受队列中第 $k$ 贵的菜,而前面的人又都吃了尽可能多的才,则说明这道菜只能由普通人吃,将其弹出并记录,然后考虑队列中第 $(k+1)$ 贵的菜;否则当前队列比第 $k$ 贵的菜便宜的菜他都吃得起,就让他尽可能去吃这些菜,然后考虑第 $(j+1)$ 个贫穷的人。
考虑完所有贫穷的人后,队列里剩下的菜和被记录的菜由剩下的 $(n−p−q)$ 个普通人解决。最后判断普通人能否吃完即可。
实现
考虑挑剔的人时可以用两个指针,分别记录当前考虑到了哪道菜、哪个人,待处理队列使用优先队列(按照价格从大到小)。假设当前二分的答案为 $week$,则每个人最多可以吃掉 $week$ 道菜。最后可以用一个变量 $tot$ 记录有几道菜没被吃而被弹出(被记录的菜),设最终优先队列里还剩下 $siz$ 道菜,如果 $tot+siz≤week(n−p−q)$ 则当前答案 $week$ 合法。
#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;
}