[USACO17DEC] My Cow Ate My Homework S

GitHub跳转原题关系图返回列表

把每个 K 对应的剩余作业看成一个后缀,预处理后缀和与后缀最小值,就能在线性时间内求出删去最小值后的最大平均分。

OJ: luogu

题目 ID: P4086

难度:普及-

标签:后缀和最小值分数比较思维

日期: 2026-06-21 01:56

题意

给定一串作业分数 a[1..N]

如果前 K 道题被吃掉,那么只剩下后缀 [K+1, N]

老师会在这段剩余作业中删去一个最小分,然后对其他题目求平均分。

要求输出所有能让这个平均分最大的 K

思路

先看最朴素的做法:

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100000 + 5;

int n;
int a[MAXN];

bool greater_fraction(long long num1, long long den1, long long num2, long long den2) {
    return num1 * den2 > num2 * den1;
}

bool equal_fraction(long long num1, long long den1, long long num2, long long den2) {
    return num1 * den2 == num2 * den1;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    long long best_num = -1;
    long long best_den = 1;
    vector<int> answer;

    for (int k = 1; k <= n - 2; k++) {
        long long sum = 0;
        int mn = 1000000000;

        // 直接暴力统计剩余题目的总分与最小值。
        for (int i = k + 1; i <= n; i++) {
            sum += a[i];
            mn = min(mn, a[i]);
        }

        long long current_num = sum - mn;
        long long current_den = n - k - 1;

        if (best_num == -1 || greater_fraction(current_num, current_den, best_num, best_den)) {
            best_num = current_num;
            best_den = current_den;
            answer.clear();
            answer.push_back(k);
        } else if (equal_fraction(current_num, current_den, best_num, best_den)) {
            answer.push_back(k);
        }
    }

    for (int i = 0; i < (int)answer.size(); i++) {
        cout << answer[i] << '\n';
    }

    return 0;
}

brute.cpp 直接枚举每个 K,然后暴力扫描后缀 [K+1, N]

  • 求这段总和
  • 求这段最小值
  • 算删去最小值后的平均分

这个做法显然正确,但如果每个 K 都重新扫一遍后缀,复杂度会到 O(N2)O(N^2)

优化的关键在于:对每个 K,我们真正关心的只有后缀的两个信息:

  1. 后缀和
  2. 后缀最小值

这两个量都可以从右往左预处理。

设:

  • suffix_sum[i] 表示后缀 [i, N] 的总和
  • suffix_min[i] 表示后缀 [i, N] 的最小值

如果剩余作业从 start 开始,也就是 start = K + 1,那么删去一个最小值后的平均分就是:

(suffix_sum[start]suffix_min[start])/(Nstart)(suffix\_sum[start] - suffix\_min[start]) / (N - start)

因为:

  • 原后缀长度是 N - start + 1
  • 去掉一个最小值后,剩余题数是 N - start

然后把所有 start = 2..N-1 枚举一遍,找出平均分最大的所有位置即可。

实现时不要用 double 直接比较平均分,使用交叉相乘比较两个分数更稳妥。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100000 + 5;

int n;
int a[MAXN];
int suffix_min[MAXN];
long long suffix_sum[MAXN];

// 返回 true 表示 num1 / den1 > num2 / den2
bool greater_fraction(long long num1, long long den1, long long num2, long long den2) {
    return num1 * den2 > num2 * den1;
}

// 返回 true 表示 num1 / den1 == num2 / den2
bool equal_fraction(long long num1, long long den1, long long num2, long long den2) {
    return num1 * den2 == num2 * den1;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    suffix_sum[n] = a[n];
    suffix_min[n] = a[n];
    for (int i = n - 1; i >= 1; i--) {
        suffix_sum[i] = suffix_sum[i + 1] + a[i];
        suffix_min[i] = min(suffix_min[i + 1], a[i]);
    }

    long long best_num = -1;
    long long best_den = 1;
    vector<int> answer;

    for (int start = 2; start <= n - 1; start++) {
        // 吃掉前 start-1 题后,剩下的是 [start, n]
        // 去掉这一段中的最小值,再对剩下的题求平均分。
        long long current_num = suffix_sum[start] - suffix_min[start];
        long long current_den = n - start;

        if (best_num == -1 || greater_fraction(current_num, current_den, best_num, best_den)) {
            best_num = current_num;
            best_den = current_den;
            answer.clear();
            answer.push_back(start - 1);
        } else if (equal_fraction(current_num, current_den, best_num, best_den)) {
            answer.push_back(start - 1);
        }
    }

    for (int i = 0; i < (int)answer.size(); i++) {
        cout << answer[i] << '\n';
    }

    return 0;
}

复杂度

预处理后缀和:O(N)O(N)

预处理后缀最小值:O(N)O(N)

枚举所有 KO(N)O(N)

总时间复杂度:

O(N)O(N)

空间复杂度:

O(N)O(N)

总结

这题表面上是在枚举被吃掉多少题,实际上是在枚举“从哪里开始看后缀”。

一旦把每个方案统一表示成一个后缀,题目就只剩下两个后缀统计量:

  • 后缀和
  • 后缀最小值

这也是这道题最核心的建模转换。