题解:洛谷 P8897 Cow College

【题目来源】

洛谷:P8897 [USACO22DEC] Cow College B - 洛谷

【题目描述】

Farmer John 计划为奶牛们新开办一所大学!

\(N(1 \le N \le 10^5)\) 头奶牛可能会入学。每头奶牛最多愿意支付 \(c_i\) 的学费 \((1 \le c_i \le 10^6)\)。Farmer John 可以设定所有奶牛入学需要支付的学费。如果这笔学费大于一头奶牛愿意支付的最高金额,那么这头奶牛就不会入学。Farmer John 想赚尽可能多的钱,从而可以给他的讲师提供一笔可观的工资。请求出他能赚到的钱的数量,以及此时应当收取多少学费。

【输入】

输入的第一行包含 \(N\)

第二行包含 \(N\) 个整数 \(c_1,c_2, \cdots,c_N\),其中 \(c_i\) 是奶牛 \(i\) 愿意支付的最高学费金额。

【输出】

输出 Farmer John 可以赚到的最大金额以及最优情况下他应该收取的学费。如果有多个解,输出收取学费最小的解。

注意这个问题涉及到的整数可能需要使用 64 位整数型(例如,Java 中的 "long",C/C++ 中的 "long long")。

【输入样例】

4
1 6 4 6

【输出样例】

12 4

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛愿意支付的最高学费 \(c_1, c_2, \dots, c_N\),Farmer John 需设定统一学费 \(p\)。愿意支付 \(\geq p\) 的奶牛会入学,总收入为 \(p \times \text{入学人数}\)。求最大收入及对应的最小最优学费。本质是一个排序后枚举定价的贪心问题。

  2. 算法选择

    • 排序 + 枚举定价:将奶牛按愿意支付金额排序,枚举每个可能的定价点,计算对应收入,取最大值
  3. 关键步骤

    • 排序:将 \(c[1..N]\) 按升序(或降序)排列
    • 枚举定价(以升序为例,\(i\)\(1\)\(N\)):
      • 若定价为 \(c[i]\),则所有愿意支付 \(\geq c[i]\) 的奶牛入学,人数为 \(N - i + 1\)
      • 收入:\(\text{total} = c[i] \times (N - i + 1)\)
      • 擂台法更新:若 \(\text{total} > \text{ans}\),更新 \(\text{ans} = \text{total}\)\(\text{ansx} = c[i]\)
    • 输出答案\(\text{ans}\)\(\text{ansx}\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),排序 \(O(N \log N)\),枚举 \(O(N)\)
    • 空间复杂度:\(O(N)\),存储学费数组
  5. 排序后枚举定价的核心思想

    • 离散最优定价点:最优定价一定等于某头奶牛愿意支付的金额(定价在 \(c[i]\)\(c[i+1]\) 之间时,入学人数相同但收入更低)
    • 单调性利用:排序后,定价越高入学人数越少,但单人支付更多;定价越低入学人数越多,但单人支付更少。收入是单峰函数,枚举所有离散点即可找到峰值
    • 最小最优学费处理:使用严格大于 > 更新最大值,当收入相同时不更新,保证保留最先遇到的最小定价;或使用 >= 配合降序排序实现同样效果
    • 64位整数注意\(N \leq 10^5\)\(c_i \leq 10^6\),收入最大可达 \(10^{11}\),需用 long long
    • 适用于定价策略、收益最大化、离散最优枚举类问题

【解题思路】

【算法标签】

普及- #其他排序

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, a[100005], mark;
long long ans=1;
bool cmp(int x, int y)  // 比较函数,按照从大到小排列
{
    return x > y;
}
int main()
{
    cin >> n;  // 输入n
    int tmp;  // 定义临时变量,用来与最大值进行比较
    for (int i=1; i<=n; i++) {  // 遍历n头奶牛
        cin >> a[i];  // 记录每个奶牛可支付的学费
    }
    sort(a+1, a+n+1, cmp);  // 按照从大到小排列

    for (long long i=1; i<=n; i++) {  // i必须使用long long类型,否则有8个测试点会错误
        long long tmp = i*a[i];  // 定义临时变量,记录这个人及之前可赚的学费
        if (tmp >= ans) {  // 擂台法,使用大于等于记录同一答案中,学费最小的解。
            ans = tmp;  // 记录最大值
            mark = a[i];  // 记录最大值时最小的学费
        }
    }
    cout << ans << " " << mark << endl;  // 输出结果
    return 0;
}
// 学而思课堂笔记
#include<bits/stdc++.h>
using namespace std;

int n, c[100005], ansx;  // 总金额最高时,学费的最小值
long long total, ans;  //
int main()
{
        cin >> n;
        for (int i=1; i<=n; i++) cin >> c[i];
        sort(c+1, c+n+1);
        for (int i=1; i<=n; i++) {
                total = 1ll*c[i]*(n-i+1);
                if (total > ans) {
                        ans = total;
                        ansx = c[i];
                }
        }
        cout << ans << " " << ansx << endl;
        return 0;
}

【运行结果】

4
1 6 4 6
12 4
posted @ 2026-08-29 09:40  团爸讲算法  阅读(11)  评论(0)    收藏  举报