上海计算机学会 2023年10月月赛 乙组T2 社团展示(贪心、思维、二分答案)

文章讨论了一道关于社团合作完成作品的问题,原采用贪心策略未能得到最优解。作者通过分析给出一个更优的方法:利用二分查找确定作品数量,确保社团人数满足条件时最大化作品完成数。并通过示例说明了贪心策略的局限性及其修正方法。
摘要由CSDN通过智能技术生成

第二题: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;
}
  • 37
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值