第二题:T2社团展示
标签:贪心、思维、二分答案
题意:给定
n
n
n个社团,第
i
i
i个社团
x
i
x_i
xi名学生,需要去完成作品。每件作品得有至少
m
m
m个不同的社团成员合作完成,每个同学只能参与一个作品,求最多完成作品数量。
题解 1(部分正确):比较容易想到一个贪心策略:每次用人数最多的
m
m
m个社团去完成
m
i
n
{
a
i
}
min \{a_i \}
min{ai}个作品,可以通过优先队列去维护,每次拿出人数最多的
m
m
m个社团,能够形成的作品数目是当前拿出的
m
m
m个社团中最少人数那个社团,都减一下,然后扔回优先队列,不断模拟这个过程,直到优先队列中的社团个数不够
m
m
m个。
测了下,发现只有部分正确,思考一下这个策略问题出在哪?
可以看看以下这个样例:
6 2
8 8 9 10 1 4
序列:10 9 8 8 4 1
第一轮:10 9,答案9,序列变成:8 8 4 1 1
第二轮:8 8,答案 9 + 8,序列变成:4 1 1
第三轮:4 1,答案 9 + 8 + 1,序列变成:3 1
第四轮:3 1,答案 9 + 8 + 1 + 1,序列变成:2,最终答案为:19
但是实际上有更多完成数量的选择:
第一轮:8 9,答案 8,序列变成:10 8 4 1 1
第二轮:10 8,答案 8 + 8,序列变成:4 2 1 1
第三轮:4 2,答案 8 + 8 + 2,序列变成:2 1 1
第四轮:2 1,答案 8 + 8 + 2 + 1,序列变成:1 1
第五轮:1 1,答案 8 + 8 + 2 + 1 + 1,最终答案为 20
以上推理得到这个贪心策略是错误的。
代码 1:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
priority_queue<ll> q;
ll a[100005];
int main() {
ll n, m, x, ans = 0;
cin >> n >> m;
for (ll i = 1; i <= n; i++) {
cin >> x;
q.push(x);
}
while (q.size() >= m) {
for (ll i = 1; i <= m; i++) {
a[i] = q.top();
q.pop();
}
ans += a[m];
for (ll i = m; i >= 1; i--) {
if (a[i] - a[m] > 0) q.push(a[i] - a[m]);
}
}
cout << ans << endl;
return 0;
}
题解 2:这道题可以考虑直接二分答案(
m
i
d
mid
mid:作品数量),如果当前社团人数不少于作品数量
m
i
d
mid
mid,我们直接
c
n
t
+
1
cnt+1
cnt+1,每个作品这个社团都得出一个人;否则,直接
s
u
m
sum
sum把当前社团人数加起来,小于作品数量的社团一定存在不重叠的方案。本质来说就是挨个摞,摞完一个
m
i
d
mid
mid再摞下一堆。
最终判定一下
s
u
m
/
m
i
d
+
c
n
t
sum/mid+cnt
sum/mid+cnt和
m
m
m大小关系,对应调整作品数量的搜寻区间即可。
代码 2:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll x[100005];
int main() {
ll n, m, ans = 0;
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> x[i];
ll l = 0, r = 1e18;
while (l <= r) {
ll mid = (l + r) >> 1;
// cnt: 社团人数超过当前枚举作品数量的个数
// sum: 不超过当前枚举作品数量的人数
ll cnt = 0, sum = 0;
for (int i = 1; i <= n; i++) {
if (x[i] < mid) sum += x[i];
else cnt++;
}
if (sum / mid + cnt >= m) {
l = mid + 1;
ans = mid;
}
else {
r = mid - 1;
}
}
cout << ans;
return 0;
}