金牌厨师(二分、差分)
题意
一个厨师可以做出辣度范围是\([1,n]\)的菜。现在有\(m\)个同学,每个同学可以接受的辣度范围是\([l_i, r_i]\)。厨师每天会选择一部分同学,做出让他们都满意的菜。满意程度定义为选出的同学的人数\(k\)和能让这部分同学都接受的菜的种类数\(x\)(这里理解为一种辣度对应一种菜)两者中的最小值,即\(\min(k,x)\)。求满意程度的最大值是多少。
数据范围
\(1 \leq n, m \leq 3 \times 10^5\)
思路
这种最小值最大的问题,首先想到的是二分答案。
首先有一个性质,就是选择的菜品辣度一定是一个连续的区间。因为可以看作是\(k\)个区间取交集,最终得到的一定是连续区间。
那么,假设当前二分到的答案是\(x\),也就是判断是否存在一个长度不小于\(x\)的区间,能够满足至少\(x\)个人。
对于每个同学,要满足他的口味,\([l_i, r_i]\)区间内的所有值都可以,因此就可以利用差分进行区间加分。
但是在实际操作过程中,有一个trick,就是我们看作\([l_i, r_i]\)区间内任意一个长度为\(x\)的子区间都可以,那么利用差分对区间中的每一个子区间都\(+1\),即可以对每个长度为\(x\)的子区间的右端点\(+1\)。求完前缀和后,判断是否有一个点的值\(\geq x\)即可。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int, int> pii;
const int N = 300010;
int n, m;
pii a[N];
int sum[N];
bool check(int x)
{
memset(sum, 0, sizeof sum);
for(int i = 1; i <= m; i ++) {
int l = a[i].first, r = a[i].second;
int len = r - l + 1;
if(len >= x) {
sum[l + x - 1] ++;
sum[r + 1] --;
}
}
for(int i = 1; i <= m; i ++) {
sum[i] += sum[i - 1];
if(sum[i] >= x) {
return true;
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i ++) {
int x, y;
scanf("%d%d", &x, &y);
a[i] = {x, y};
}
int l = 1, r = n;
while(l < r) {
int mid = l + r + 1 >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
printf("%d\n", l);
return 0;
}

浙公网安备 33010602011771号