题目描述
难度分:1400
输入T(≤104)表示T组数据。所有数据的m之和≤2×105。
每组数据输入n,m(1≤n≤m≤2×105)和长为m的数组a(1≤a[i]≤n)。
有n名工人和m个任务,a[i]表示擅长第i个任务的工人编号。
每个任务只能分配给一名工人。如果工人擅长该任务,他会用1小时完成,否则需要2小时完成。
一名工人一次只能做一个任务,完成一个任务后,才能开始他的下一个任务。所有工人同时开始工作。输出完成所有任务最少要多少小时。
输入样例
4
2 4
1 2 1 2
2 4
1 1 1 1
5 5
5 1 3 2 4
1 1
1
输出样例
2
3
1
1
算法
二分答案
时间给得越多显然就越能完成任务,因此用一个数组cnt记录每个工人擅长的任务数量cnt[a[i]],然后二分完成任务的时间,对于一个给定的时间x:
- 如果工人i擅长的任务数量cnt[i]≥x,那他就能花x的时间干完x个擅长的任务;
- 如果工人i擅长的任务数量cnt[i]<x,那他只能花cnt[i]的时间干完cnt[i]个擅长的任务,剩余的x−cnt[i]时间要干[x−cnt[i]2]个不擅长的任务(其中[.]表示对.向下取整)。
遍历所有i∈[1,n]计算每个工人干的任务数:如果干完的任务数不小于m,说明这个时间x是可以的,往左搜索看能不能更小;否则就要往右搜索,宽限完成任务的时间。
复杂度分析
时间复杂度
二分的时间复杂度是O(log2m),每次二分的check要遍历i∈[1,n],时间复杂度为O(n)。因此,算法整体的时间复杂度为O(nlog2m)。
空间复杂度
在二分的过程中需要一个cnt数组来记录每个工人擅长的任务数,而这个数组的空间消耗是O(n)的,这也是整个算法的额外空间复杂度。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
int T, n, m, a[N], cnt[N];
bool check(int x) {
long long sum = 0;
for(int i = 1; i <= n; i++) {
if(cnt[i] >= x) {
sum += x;
}else {
sum += cnt[i] + (x - cnt[i]) / 2;
}
}
return sum >= m;
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
memset(cnt, 0, sizeof cnt);
for(int i = 1; i <= m; i++) {
scanf("%d", &a[i]);
cnt[a[i]]++;
}
int l = 1, r = 2*m;
while(l < r) {
int mid = l + r >> 1;
if(check(mid)) {
r = mid;
}else {
l = mid + 1;
}
}
printf("%d\n", r);
}
return 0;
}