第二题: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;
}