每日一题:成绩统计

来源:2024蓝桥杯省赛大学A组

P10389 成绩统计 - 洛谷

成绩统计 - 蓝桥云课

题解

首先观察数据范围,注意到数据的量级是1e5,所以纯暴力枚举所有答案的做法肯定是过不了的,需要进行优化。

题干中出现“至少要检查多少个人成绩”,换言之就是再问最小值。那么当题目问的是最小/最大值时,我们就可以思考一下二分的做法能不能解出这道题了,要是解不出来我们就接着思考其他方法。

那么对于这道题的二分做法:对[k, n]的范围,二分答案“检查同学数mid”,判断mid是否满足题目的要求。那么这部分的二分代码就好实现了。

int l = k, r = n;
    int ans = INF;
    while (l <= r)
    {
        int mid = l + ((r - l) >> 1);
        if (check(mid))
        {
            ans = mid;
            r = mid - 1;
        }
        else
            l = mid + 1;
    }
    if (ans == INF) //没有找到答案
        cout << -1 << '\n';
    else
        cout << ans << '\n';

那么接下来的关键就是check()的实现,接下来首先需要运用一些数学知识对给出的方差公式进行展开。

\[\sum_{i=1}^{k}{(v_i-\overline{v})^2}=\sum_{i=1}^{k}{v_i^2}-2\overline{v}\sum_{i=1}^{k}{v_i}+k\overline{v}^2 \]

但是对每个check函数如果枚举计算每一项,时间复杂度仍然会很高,所以我们可以用前缀和提前处理数据,这样在每次计算时就是O(1)的时间复杂度。

vector<int> ssum(x + 1, 0), spf(x + 1, 0); // v_i的前缀和与平方的前缀和
for (int i = 1; i <= x; i++)
{
    ssum[i] = ssum[i - 1] + b[i];
    spf[i] = spf[i - 1] + b[i] * b[i];
}

首先对[1, mid]范围进行排序,就减小了连续的数之间大小的差异,使得连续的k个数方差最小。再通过计算[1, mid]范围内,所有连续的k个数的方差是否小于1,check()返回True/False。

完整代码

//代码来源byboyou,反对直接复制粘贴题解抄袭的行为
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int INF = 0x3f3f3f3f3f3f3f3f;

int n, k, t;
vector<int> a;

bool check(int x)
{
    vector<int> b(x + 1);
    for (int i = 1; i <= x; i++)
        b[i] = a[i];
    sort(b.begin() + 1, b.end());
    vector<int> ssum(x + 1, 0), spf(x + 1, 0); // 前缀和与平方的前缀和
    for (int i = 1; i <= x; i++)
    {
        ssum[i] = ssum[i - 1] + b[i];
        spf[i] = spf[i - 1] + b[i] * b[i];
    }

    double avg = 0; // v的均值
    for (int i = 1; i <= k; i++)
        avg += b[i] / (double)k;
    double var = (spf[k] - 2 * avg * ssum[k] + k * avg * avg) / (double)k;
    if (var < t)
        return 1;

    for (int i = k + 1; i <= x; i++)
    {
        avg = avg - b[i - k] / (double)k + b[i] / (double)k;
        var = ((spf[i] - spf[i - k]) - 2 * avg * (ssum[i] - ssum[i - k]) + k * avg * avg) / (double)k;
        if (var < t)
            return 1;
    }
    return 0;
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n >> k >> t;
    a.resize(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    int l = k, r = n;
    int ans = INF;
    while (l <= r)
    {
        int mid = l + ((r - l) >> 1);
        if (check(mid))
        {
            ans = mid;
            r = mid - 1;
        }
        else
            l = mid + 1;
    }
    if (ans == INF)
        cout << -1 << '\n';
    else
        cout << ans << '\n';

    return 0;
}