蓝桥杯刷题015——最少刷题数(二分法+前缀和)

news/2024/11/22 16:12:12/

问题描述

小蓝老师教的编程课有 N 名学生, 编号依次是 1…N 。第 i 号学生这学期刷题的数量是 Ai​ 

对于每一名学生, 请你计算他至少还要再刷多少道题, 才能使得全班刷题比他多的学生数不超过刷题比他少的学生数。

输入格式

第一行包含一个正整数 N 。

第二行包含 N 个整数:A1​,A2​,A3​,…,AN​.                         

输出格式

输出 N 个整数, 依次表示第 1…N 号学生分别至少还要再刷多少道题。

样例输入

5
12 10 15 20 6

样例输出

0 3 0 0 7

评测用例规模与约定

对于 30% 的数据,1≤N≤1000,0≤Ai​≤1000.

对于 100% 的数据, 1≤N≤100000,0≤Ai​≤100000.

题目大意

给出一个序列表示每个人目前的刷题数,计算每个人最少再刷多少题,可使得刷题数比他多的人数不超过刷题数比他少的人数。

解题关键:

给出一个数字,计算出序列中比它大的数字个数和比它小的数字个数

解题思路一:二分        算法复杂度:O(n (logn)^2)

  • 给定一个数字,可以通过二分查找的方式确定它在一个有序序列中所处的下标,根据下标位置,可以确定大于它的数字数和小于它的数字数,从而完成“多刷题人数”和“少刷题人数”的确定,假设用more和fewer来表示。
  • 小的刷题数对应较大的more和较小的fewer,大的刷题数对应较小的more和较大的fewer,因此根据这样的有序性,我们可以对每个同学的刷题数使用二分算法,找出使more<=fewer的最小值。
  • check()函数设置为检查more<=fewer,满足返回True,否则返回False。复杂度为O(logn)

参考代码1:通过70%数据测试 

import bisect
n = int(input())
a = list(map(int, input().split()))
sa = sorted(a)                              #获取一份和a相同的,但经过排序的列表# #判断刷题数为cur时,刷题数比他多的人数是否不超过比他小的人数
def check(cur, raw):                        few = bisect.bisect_left(sa, cur)       #使用二分的方式,通过查找元素位置,计算数目    fewer = few if cur == raw else few - 1  #当cur的值相较原本已经增加,这时判断少刷题人数要扣掉自己more = len(sa) - bisect.bisect_right(sa, cur)   #计算多刷题的人数if more <= fewer:                       #判断情况return Trueelse:return Falsemax_num = sa[-1]                            #获取所有人中刷题最多的题数,作为二分的右边界
res = []                                    #记录每人为实现目标要多刷的题数
for i in range(n):l, r = a[i] - 1, max_num + 1            #l,r是新的刷题数,标定左右边界while l + 1 != r:                       #二分算法mid = l + r >> 1if not check(mid, a[i]):            #刷题数不达标,说明当前题数还较少,压缩左边界l = midelse:r = mid                         #否则压缩右边界res.append(r - a[i])                    #最终符合要求的结果在r所指范围中,用r减去a[i]即可得到需要多刷的题数
print(*res)

优化一:对check()优化:前缀和

使用前缀和避免重复计算              算法复杂度:O(nlogn)

示例:5个学生,刷题数分别是12,10,8,15,6

1、建一个列表cnt统计每个刷题数对应的学生数
2、计算该列表的前缀和列表scnt,特别赋值scnt[0]=cnt[0],因为本题Ai是从0开始的,但其实前缀和的0一般不用,是从1开始的。

计算前缀和scnt[i]= \sum_{j=0}^{i}cnt[j]]  或者    scnt[i]=scnt[i-1]+cnt[i]

例如:刷题数为10的人,比他多的人more可以用scnt[M] - scnt[cur]=5-3=2,比他少的人fewer可以用前一个前缀和scnt[cur-1] =  scnt[cur] - cnt[cur]=3-1=2,如果是通过再刷题达到的刷题数cur,那么fewer需要再-1,减掉一个再刷题前的自己。(因为需要再刷题说明肯定比之前刷题数多)

参考代码2:通过100%数据测试  

n = int(input())
a = list(map(int, input().split()))
N = 100010                          # 稍微比范围大一点
cnt = [0] * N; scnt = [0] * N       # 最后多的0没有影响
for i in range(n):cnt[a[i]] += 1                  # 统计刷题数为ai的人数scnt[0] = cnt[0]                    #  特别赋值
for i in range(1, N):scnt[i] = scnt[i - 1] + cnt[i]  # 刷题数i的前缀和def check(x, old):more = scnt[N - 1] - scnt[x]    # more:所有人数 - 刷题数x的前缀和fewer = scnt[x] - cnt[x]        # fewer:刷题数x的前缀和 - 刷题数x的人数if x != old:                    # 需要达到刷题数和原来刷题数不一样fewer -= 1                  # 减掉一个原来的自己if more <= fewer:return Trueelse:return Falseres = []                            # 记录每人为实现目标要多刷的题数
for i in range(n):l, r = a[i] - 1, 100001while l + 1 != r:mid = l + r >> 1            # 右移1等价于//2if check(mid, a[i]):r = midelse:l = midres.append(r - a[i])            # 需要多刷的题数 = 最少需要达到的刷题数r - 原来刷题数a[i]
print(*res)                         # 加个*,可以打印出列表的每个元素,用空格隔开

优化二:前缀和+一次二分找中间数

        通过前面的做法,我们发现,对于需要多刷题的人,他们最后都会刷到同一个题目数。例如样例中他们最后都会刷到同一个题目数13.
        因此,我们选出一个最小的当前刷题数,用一次二分查找即可确定将要刷到的题数,记为pos
之后遍历a中所有数字,check()不需要再二分查找pos,如果满足check(),就说明新刷题数是0,否则新刷题数是pos - a[i]
算法复杂度:O(n)

参考代码3:通过100%数据测试 

n = int(input())
a = list(map(int, input().split()))
t = min(a)                            # 选出刷题数最少的,后面进行一次二分查找
N = 100010
cnt = [0] * N; scnt = [0] * N
for i in range(n):cnt[a[i]] += 1scnt[0] = cnt[0]
for i in range(1, N):scnt[i] = scnt[i - 1] + cnt[i]def check(x, old):more = scnt[N - 1] - scnt[x]fewer = scnt[x] - cnt[x]if x != old:fewer -= 1if more <= fewer:return Trueelse:return False# 没有for循环,只需要做一次二分
l, r = t - 1, 100001
while l + 1 != r:mid = l + r >> 1if check(mid, t):r = midelse:l = mid
pos = r    # 最少需要达到的刷题数pos
res = []
for i in range(n):               if check(a[i], a[i]):        # 已经满足more <= fewerres.append(0)            # 不需要刷题else:                        # 不满足more <= fewerres.append(pos - a[i])   # 需要多刷的题数 = 最少需要达到的刷题数 - 原来刷题数
print(*res)


 


http://www.ppmy.cn/news/19952.html

相关文章

2023-1-26 刷题情况

具有给定数值的最小字符串 题目描述 小写字符 的 数值 是它在字母表中的位置&#xff08;从 1 开始&#xff09;&#xff0c;因此 a 的数值为 1 &#xff0c;b 的数值为 2 &#xff0c;c 的数值为 3 &#xff0c;以此类推。 字符串由若干小写字符组成&#xff0c;字符串的数…

Linux pdflush机制

在做进程安全监控的时候&#xff0c;拍脑袋决定的&#xff0c;如果发现一个进程在D状态时&#xff0c;即TASK_UNINTERRUPTIBLE&#xff08;不可中断的睡眠状态&#xff09;&#xff0c;时间超过了8min&#xff0c;就将系统panic掉。恰好DB组做日志时&#xff0c;将整个log缓存到…

linux 网络编程socket

前言 socket&#xff08;套接字&#xff09;是linux下进程间通信的一种方式&#xff0c;通常使用C-S&#xff08;客户端-服务端&#xff09;的方式通信&#xff0c;它可以是同一主机下的不同进程间通信或者不同主机的进程通信。 socket是夹在应用层和TCP/UDP协议层间的软件抽象…

MyBatis(一)MyBatis概述

一、什么是框架 ● 在文献中看到的framework被翻译为框架 ● java常用的框架&#xff1a; SSM三大框架&#xff1a;Sping SpringMVC MyBatisSpringBootSpringCloud● 框架其实就是对通用代码的封装&#xff0c;提前写好了一堆接口和类&#xff0c;我们可以在做项目的时候直…

SNARK+深度神经网络

1. 引言 SNARK深度神经网络&#xff0c;相关开源实现有&#xff1a; 1&#xff09;Ezkl&#xff08;Rust&#xff09;&#xff1a;借助Halo2证明系统&#xff0c;实现了50层的MobileNetV2的执行证明。具体见Daniel Kang等人2022年论文Scaling up Trustless DNN Inference with…

2023年web类第一期总结

&#x1f340;本人简介&#xff1a; 吉师大一最爱逃课的网安混子、 华为云享专家、阿里云专家博主、腾讯云自媒体分享计划博主、 华为MindSpore优秀开发者、迷雾安全团队核心成员&#xff0c;CSDN2022年运维与安全领域第15名 &#x1f341;本人制作小程序以及资源分享地址&am…

【Linux】六、Linux 基础IO(四)|动态库和静态库

目录 十一、动态库和静态库 11.1 动态库和静态库定义 11.2 动静态库的基本原理 11.3 静态库的打包与使用 11.3.1 静态库的打包 11.3.2 静态库的使用 11.4 动态库的打包与使用 11.4.1 动态库的打包 11.4.2 动态库的使用 11.5 动态库的加载 十一、动态库和静态库 11.1…

指针与数组

目录指针运算&#xff08;补&#xff09;指针指针指针的关系运算&#xff08;补&#xff09;指针与数组数组名二级指针指针数组指针运算&#xff08;补&#xff09; 指针指针 上一篇博客我们介绍了指针运算中的三种常见运算&#xff1a;指针整数&#xff0c;指针关系运算&…