最少刷题数

🎈描述

原题链接:https://www.acwing.com/problem/content/4668/

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

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

输入格式

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

第二行包含 N 个整数: A 1 , A 2 , A 3 … , A N A_{1}, A_{2}, A_{3} \ldots, A_{N} A1,A2,A3,AN,

数据范围
对于 100% 的数据, 1 ≤ N ≤ 100000 , 0 ≤ A i ≤ 100000 1≤N≤100000,0≤A_i≤100000 1N1000000Ai100000

输出格式

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

输入样例:

5
12 10 15 20 6

输出样例:

0 3 0 0 7

💡思路

  • 假设某位同学的刷题数是A,根据题目,需要知道刷题比A多的人数 以及 刷题比A少的人数

    • 查询某一刷题数区间内的 人数
    • 即用到前缀和数组,设前缀和数组是pre
    • 可知, pre[A]代表 刷题数在[1,A] 范围的人数
    • 刷题比A多的人数表示为: pre[100000]-pre[A]
      • 即刷题数在[A+1,100000] 区间的人数,(根据数据范围A最大是100000)
    • 刷题比A少的人数表示为: pre[A-1]
      • 即刷题数在[1,A-1] 区间的人数
      • 注意A=0时,显然数组越界,A=0即刷题比0少的人数,为0,写代码时处理一下即可
    • 刷题比 A 多的人数 不超过 刷题比 A 少的人数 \color{red}{刷题比A多的人数}\color{blue}{不超过}\color{green}{刷题比A少的人数} 刷题比A多的人数不超过刷题比A少的人数表示为:
      • p r e [ 100000 ] − p r e [ A ] < = p r e [ A − 1 ] \color{red}{pre[100000]-pre[A]} \color{blue}{ <=}\color{green}{pre[A-1]} pre[100000]pre[A]<=pre[A1]
  • 遍历a数组

    • 如果 A i A_i Ai已经满足条件, p r e [ 100000 ] − p r e [ A i ] < = p r e [ A i − 1 ] pre[100000]-pre[A_i]<=pre[A_i-1] pre[100000]pre[Ai]<=pre[Ai1]
      • 直接输出0即可
    • 否则
      • 希望找到一个满足条件的 目标刷题数 T
        • A i A_i Ai刷不够T这个数,不满足条件; 刷到T了,恰好满足条件;继续刷更满足条件
          • 因为刷的题目越多 刷题比我多的人越会变少,比我少的人越会变多
        • 即 这个T 要尽量小,因为求的是最少刷题数
        • 因此,T就满足二段性,可以二分找到T
          • image-20230212142337187
        • 那么 A i A_i Ai要达到T才满足条件 即 A i A_i Ai至少再刷T- A i A_i Ai道题
        • 要特别注意一个细节
          • 从A刷到T: A …T
          • 那么站在T的视角看看T是否满足条件,比T少的人数 要减1
          • 因为原先有一个A,刷A题的人数比T的人数少,这个A已经刷到T了,即刷A题的人数少了1,所以比T少的人数 要减1
  • 时间复杂度O(nlogn)

📝代码

import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Main {
	static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
	static final int N=100000;
	static int []a=new int[N+5];
	static int []pre=new int[N+5];
	static int n;
	public static void main(String[] args) throws Exception {
		n=Integer.parseInt(br.readLine());
		String []s=br.readLine().split(" ");
		for(int i=1;i<=n;i++) {
			a[i]=Integer.parseInt(s[i-1]);
			pre[a[i]]++;
		}
		//求前缀和
		for(int i=1;i<=100000;i++)
			pre[i]+=pre[i-1];
		for(int i=1;i<=n;i++) {
			//满足条件直接输出0
			if(pre[100000]-pre[a[i]]<=pre[Math.max(a[i]-1, 0)]) {
				System.out.print(0);
				if(i<n)System.out.print(" ");
				continue;
			}
			//二分的边界,a[i]不满足条件,a[i]+1可能满足条件,因此左边界是a[i]+1
			int l=a[i]+1;
			int r=N;
			while(l<r) {
				int m=(l+r)>>1;
				if(pre[100000]-pre[m]<=pre[Math.max(m-1, 0)]-1) //注意-1这个细节
					r=m;
				else
					l=m+1;
			}
			System.out.print(l-a[i]);
			if(i<n) System.out.print(" ");		
		}
	}
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值