蓝桥杯2024年第十五届省赛A组-成绩统计

题目描述

小蓝的班上有 n 个人,一次考试之后小蓝想统计同学们的成绩,第 i 名同学的成绩为 ai 。当小蓝统计完前 x 名同学的成绩后,他可以从 1 ∼ x 中选出任意 k 名同学的成绩,计算出这 k 个成绩的方差。小蓝至少要检查多少个人的成绩,才有可能选出 k 名同学,他们的方差小于一个给定的值 T ?

提示:k 个数 v1, v2, · · · , vk 的方差 \sigma^2 定义为:\sigma^2 = \frac{1}{k} \sum_{i=1}^{k} \left ( v_i - \bar{v} \right )^{2} ,其中 \bar{v} 表示 v 的平均值,\bar{v} = \frac{1}{k} \sum_{i=1}^{k} v_i 。

输入格式

输入的第一行包含三个正整数 n, k, T ,相邻整数之间使用一个空格分隔。

第二行包含 n 个正整数 a1, a2, · · · , an ,相邻整数之间使用一个空格分隔。

输出格式

输出一行包含一个整数表示答案。如果不能满足条件,输出 −1 。

样例输入

5 3 1
3 2 5 2 3

样例输出

4

提示

【样例说明】

检查完前三名同学的成绩后,只能选出 3, 2, 5 ,方差为 1.56 ;检查完前四名同学的成绩后,可以选出 3, 2, 2 ,方差为 0.33 < 1 ,所以答案为 4 。

【评测用例规模与约定】

对于 10% 的评测用例,保证 1 ≤ n, k ≤ 10^2;

对于 30% 的评测用例,保证 1 ≤ n, k ≤ 10^3 ;

对于所有评测用例,保证 1 ≤ n, k ≤ 10^5 ,1 ≤ T ≤ 2^31 − 1 ,1 ≤ ai ≤ n 。

整体思路

考虑二分查找答案位置,对于前 mid 个数,先将其从小到大排序,每次从中连续地取出 k 个数,然后计算其方差。

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

int k, T;
bool check(int loc, const vector<int>& a)
{
	vector<int> b(loc + 1);
	copy(a.begin() + 1, a.begin() + 1 + loc, b.begin() + 1);
	sort(b.begin() + 1, b.begin() + 1 + loc);
	int sum = 0, sum_sq = 0; // 一次项和二次项 
	double avev; // 一次项平均数 
	for(int i = 1; i < k; i++)
	{
		sum += b[i];
		sum_sq += b[i] * b[i];
	}
	for(int i = k; i <= loc; i++)
	{
		sum -= b[i - k];
		sum += b[i];
		sum_sq -= b[i - k] * b[i - k];
		sum_sq += b[i] * b[i];
		avev = 1.0 * sum / k;
		if((sum_sq - 2 * avev * sum + k * avev * avev) / k <= T)
		{
			return true;
		}
	}
	return false;
}

signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	int n;
	cin >> n >> k >> T;
	vector<int> a(n + 1);
	for(int i = 1; i <= n; i++)
	{
		cin >> a[i];
	}
	
	if(check(n, a) == false)
	{
		cout << -1 << endl;
		exit(0);
	}
	int left = k, right = n, ans;
	while(left <= right)
	{
		int mid = left + ((right - left) >> 1);
		if(check(mid, a) == true)
		{
			ans = mid;
			right = mid - 1;
		}
		else
		{
			left = mid + 1;
		}
	}
	cout << ans << endl;
	return 0;
}

公式推导

\sum_{i=1}^{k} \left ( v_i - \bar{v} \right )^{2} = \sum_{i=1}^{k}\left ( v_i^2 - 2v_i\bar{v} + \bar{v}^2 \right ) = \sum_{i=1}^{k} v_i^2 - 2\bar{v} \sum_{i=1}^{k} v_i + k\bar{v}^2

其中 vi 的和以及平方和可以用队列的形式维护,平均值则可以通过已知条件计算得到,这样就能用线性的时间复杂度快速求出方差了。

具体步骤

1. 先将数组 a 的前 loc 拷贝到数组 b 中,之后进行排序(防止数组 a 内数据变化)。

2. 计算出 vi 前 k 项的和以及平方和,利用滑动窗口的思想保持窗口长度为 k 向后遍历,分别计算出 k 项的方差,判断是否满足题意。

3. 利用二分法搜索答案位置,注意边界条件的选取:我们所求答案 target 是在一个左闭右闭的区间里,也就是 [left, right] 。因为 left == right 是有意义的,所以 while (left <= right) 时要使用 <= 。因为当前这个 mid 一定不是 target,那么接下来要查找的左区间结束下标位置就是 mid - 1,所以right 要赋值为 mid - 1,left 同理为 mid + 1。

  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值