蓝桥杯刷题015——最少刷题数(二分法+前缀和)
创始人
2024-05-19 01:27:13
0

问题描述

小蓝老师教的编程课有 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)


 

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...