题目描述
一家弹珠厂向一所幼儿园捐赠了一些弹珠,弹珠一共有 \(M\) 种颜色,每颗弹珠都有一种颜色。老师需要把所有的弹珠分给 \(N\) 个孩子。每个孩子得到的所有弹珠都必须是相同的颜色,而且可以有一些孩子一颗弹珠也没得到。
我们把嫉妒值定义为分给一个孩子最多的弹珠数量。请你帮助老师分弹珠,使得嫉妒值最小。
例如,如果有 \(4\) 个红色的弹珠(\(\texttt{RRRR}\))和 \(7\) 个蓝色的弹珠(\(\texttt{BBBBBBB}\)),要分给 \(5\) 个孩子,那么我们可以这样划分:\(\texttt{RR}\),\(\texttt{RR}\),\(\texttt{BB}\),\(\texttt{BB}\),\(\texttt{BBB}\)。这样分的嫉妒值为 \(3\),是最小的。
\(1 \leq m \leq 3\times 10^5 ,1 \leq n \leq 10^9 ,1 \leq x \leq 10^9\) 。 \(x\) 表示每种弹珠的数量。
Solution
如果直接计算,感觉非常不可做的样子。
但是容易发现:在每个分到弹珠的孩子都分到最多的弹珠的前提下,嫉妒值越大,分到弹珠的孩子越少;嫉妒值越小,分到弹珠的孩子越少。也就是说,这个嫉妒值是有单调性的。
那么可以二分答案。把原问题转换为:是否存在一种方案,使得嫉妒值 \(\leq mid\) 。
那么对于第 \(i\) 种数量为 \(a_i\) 的弹珠,它最少需要分给 \(\lceil \dfrac{a_i}{mid} \rceil\) 个孩子。把所有的弹珠需要的孩子数量相加,就能得到最少需要分给多少个孩子。
如果最终要分给的孩子数量 \(\leq n\) ,那么这个 \(mid\) 就是可行的,就应该缩小答案,也就是令二分边界 \(r=mid\) ,否则 \(mid\) 是不可行的,应该让 \(l=mid+1\) 。
时间复杂度 \(\mathcal{O}(m \log n)\) ,足已通过。
代码如下(不开 \(\texttt{long long}\) 见祖宗):
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <queue>
typedef long long LL;
using namespace std;
inline int read() {
int num = 0 ,f = 1; char c = getchar();
while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
return num * f;
}
const int N = 3e5 + 5 ,INF = 0x3f3f3f3f;
int a[N] ,n ,m;
inline bool check(LL mid) {
LL ans = 0;
for (int i = 1; i <= m; i++)
ans += (a[i] - 1) / mid + 1; //上取整的优雅的写法。
return ans <= n;
}
LL l = 1 ,r; //记得开 long long
signed main() {
n = read(); m = read();
for (int i = 1; i <= m; i++) a[i] = read() ,r = max(r ,(LL)a[i]);
//注意 l 一定要 > 1 ,r 的话去所有弹珠数量的最大值就可以。因为最坏情况下是把所有同颜色的弹珠分给一个人。
while (l < r) {
LL mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
printf("%lld\n" ,l);
return 0;
}