当前位置: 首页 > news >正文

二分+前缀和/滑动窗口——成绩统计

成绩统计(二分+前缀和/滑动窗口)24年省赛

题目分析:

定位关键词——“至少要检查多少个人的成绩,才有可能选出 k 名同学,他们的方差小于一个给定的值 T”,也就是满足条件的最小值,可以考虑用二分。但是他需要可以二分,也就是单调性或者二段性。每次选择都是选择前x个数,然后在这前x个数里面找到一组个数为k的数,他们的方差满足条件,如果x可以找到,那么x继续增大也可以找到,因为增大后的x是包含了增大前的x的。举个例子,假设x=5。在a[1],a[2],a[3],a[4],a[5]中,我们可以找到a[2],a[3],a[5]满足方差小于T,那么当x=8时,对于a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8]也一定存在a[2],a[3],a[5]满足方差小于T,因此具有单调性,也就是小的值满足条件,大的值也一定满足条件。

接下来考虑二分过程。

第一阶段二段性分析

当我检查了前mid个同学的成绩之后,如果可以找到一组数,个数为k,且方差小于T,那么我会尝试找更小的值是不是也满足条件,所以向左边走,即

if (check(mid)) {r = mid;} //因为mid是符合条件的,所以我要留着它,而不是l=mid+1

当我检查了前mid个同学的成绩之后,如果不可以找到一组数,个数为k,且方差小于T,那么我只能找更大的值,所以向右边走,即

else { l = mid + 1; }//因为mid是不符合条件的,所以我不要留着它,而不是r=mid

综上该题满足二段性,可以用二分,二分的板子就不说了,接下来说一下check函数如何写。

第二阶段写check函数

check(mid)要实现的作用是当我知道了前mid个学生的成绩后,能否快速找出k名同学,使得他们的方差小于T。如果不能则返回false,否则返回true。我们希望选出来的k名同学的方差尽可能小,方差衡量的是数据之间的差值,自然要选择数据相近的k个值,所以我们可以对原始数据进行排序,然后选择连续的k个值就可以了。但是怎么快速计算选出来的k个值的方差呢?这就要根据方差公式了。

image-20241204170908628

对于 ∑ i = 1 k v i 2 \sum_{i=1}^k{v_i^2} i=1kvi2 ∑ i = 1 k v i \sum_{i=1}^k{v_i} i=1kvi,我们可以通过前缀和、前缀平方和或者滑动窗口的形式快速得到。而对于剩余的可以直接计算得出。所以代码如下,

static boolean check(int mid) {
        for (int i = 1; i <= mid; i++)
            b[i] = a[i];
        Arrays.sort(b, 1, mid + 1);//排序
        long v2 = 0, v = 0;
        for (int i = 1; i < k; i++) {//预处理数组b的前k-1项相关数据
            v2 += 1L * b[i] * b[i];//求平方和
            v += b[i];//求和
        }
        for (int i = k; i <= mid; i++) {
            v2 += 1L * b[i] * b[i] - 1L * b[i - k] * b[i - k];//类似滑动窗口,加上当前值,去掉已经滑出去的值
            v += b[i] - b[i - k];//类似滑动窗口,加上当前值,去掉已经滑出去的值
            double avg = 1.0 * v / k;//得到平均值
            if ((v2 - 2 * avg * v + k * avg * avg) / k < T)
                return true;
        }
        return false;
    }

刚刚的代码采用的是滑动窗口的思路,下面的代码使用的是前缀和的思路,

static boolean check(int mid) {
        for (int i = 1; i <= mid; i++)
            b[i] = a[i];
        Arrays.sort(b, 1, mid + 1);
        long[] sum_v2 = new long[mid+1], sum_v = new long[mid+1];
        for (int i = 1; i <= mid; i++) {
            sum_v2[i] = sum_v2[i-1] + 1L * b[i] * b[i];
            sum_v[i] =sum_v[i-1] + b[i];
        }
        for (int i = k; i <= mid; i++) {
            // v2 += 1L * b[i] * b[i] - 1L * b[i - k] * b[i - k];
            // v += b[i] - b[i - k];
            long v2 = sum_v2[i] - sum_v2[i-k];
            long v = sum_v[i] - sum_v[i-k];
            double avg = 1.0 * v / k;
            if ((v2 - 2 * avg * v + k * avg * avg) / k < T)
                return true;
        }
        return false;
    }

第三步二分范围确定

那么这里的高度的最小值是1,最大值就是数组中元素的个数,也就是n。注意本题没有说一定能找到符合条件的高度,所以最后输出的时候要判断一下。

题目代码

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static int n, k, T;
    static int[] a, b;

    static boolean check(int mid) {
        for (int i = 1; i <= mid; i++)
            b[i] = a[i];
        Arrays.sort(b, 1, mid + 1);
        long[] sum_v2 = new long[mid+1], sum_v = new long[mid+1];
        for (int i = 1; i <= mid; i++) {
            sum_v2[i] = sum_v2[i-1] + 1L * b[i] * b[i];
            sum_v[i] =sum_v[i-1] + b[i];
        }
        for (int i = k; i <= mid; i++) {
            // v2 += 1L * b[i] * b[i] - 1L * b[i - k] * b[i - k];
            // v += b[i] - b[i - k];
            long v2 = sum_v2[i] - sum_v2[i-k];
            long v = sum_v[i] - sum_v[i-k];
            double avg = 1.0 * v / k;
            if ((v2 - 2 * avg * v + k * avg * avg) / k < T)
                return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        k = scanner.nextInt();
        T = scanner.nextInt();
        a = new int[n + 1];
        b = new int[n + 1];
        for (int i = 1; i <= n; i++)
            a[i] = scanner.nextInt();
        int l = k, r = n;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (check(mid)) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        if(check(l))System.out.println(l);
        else System.out.println(-1);
    }
}

相关文章:

  • Linux黑科技防裁防背锅六芒星战阵
  • MySQL开发陷阱与最佳实践:第1章:MySQL开发基础概述-1.1 MySQL简介与应用场景
  • 2021-05-23 C++百元百鸡
  • Python+Selenium
  • 问题二:整形提升
  • Python基础入门掌握(八)
  • Netty 连接存活检测——如何判断连接是否断开?
  • 【设计模式】建造者模式
  • Linux系统中查询命令行解释器
  • leetcode 75.颜色分类(荷兰国旗问题)
  • 基于 Docker 搭建 FRP 内网穿透开源项目
  • 2023南京理工大学计算机复试上机真题
  • .npy文件介绍
  • 网络协议栈
  • 农资出入库登记本,农药化肥库存出入库软件,佳易王农资管理庄稼医院开单管理系统操作教程
  • Java字节码
  • C++类与对象——拷贝构造与运算符重载
  • 【论文阅读】AlexNet——深度学习奠基作之一
  • 笔记本 Win10 部署阿里通义千问 1.5-0.5B 大模型 mini 版
  • nvm安装node失败的处理方法
  • 10台核电新机组获核准,上海核电厂商独揽超500亿元订单
  • 俄罗斯延长非法滞留外国人限期离境时间至9月
  • 龚正会见委内瑞拉副总统罗德里格斯
  • 黄仁勋访华期间表示希望继续与中国合作,贸促会回应
  • 玉渊谭天丨中方减少美国农产品进口后,舟山港陆续出现巴西大豆船
  • 加拿大警方:已确认有9人在温哥华驾车撞人事件中遇难